This article explains how to refactor a FastAPI application by organizing endpoints into separate router files for better modularity and maintainability.
In this article, we refactor our FastAPI application by separating user and post path operations (CRUD operations) into distinct files. Initially, our main.py file contains all endpoints, which can lead to clutter as the application grows.
During execution, you might observe logs like these:
Copy
Ask AI
[SQL: INSERT INTO users (email, password) VALUES (%(email)s, %(password)s) RETURNING users.id][parameters: {'email': '[email protected]', 'password': '$2b$12$mvtoneBzuKAA0HgrBTDeRfJaf10F1W3oz'}](Background on this error at: https://sqlalche.me/e/14/gkpj)WARNING: WatchGodReload detected file change in '[C:\Users\sanje\Documents\Courses\fastapi\app\main.py]'. Reloading...Database connection was successful!INFO: Started server process [7824]INFO: Waiting for application startup.INFO: Application startup complete.
In addition to posts, main.py also handles user operations, such as creating a new user or retrieving a user by ID:
Copy
Ask AI
@app.get('/users/{id}', response_model=schemas.UserOut)def get_user(id: int, db: Session = Depends(get_db)): user = db.query(models.User).filter(models.User.id == id).first() if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"User with id: {id} does not exist")
Managing all endpoints in one file can become unwieldy as your application grows.
Splitting your endpoints into separate files makes your code more modular and maintainable.
To streamline our application structure, we create a new directory named routers and add two files inside it: post.py and user.py. This allows us to move all post-related operations to post.py and user-related operations to user.py.
After splitting the routes into separate files, update your main.py to include the routers from the routers directory:
Copy
Ask AI
from fastapi import FastAPIfrom . import models, schemas, utilsfrom .database import engine, get_dbfrom .routers import post, userimport psycopg2from psycopg2.extras import RealDictCursorimport timemodels.Base.metadata.create_all(bind=engine)app = FastAPI()# Database connection handlingwhile True: try: conn = psycopg2.connect( host='localhost', database='fastapi', user='postgres', password='password123', cursor_factory=RealDictCursor ) cursor = conn.cursor() print("Database connection was successful!") break except Exception as error: print("Connecting to database failed") print("Error: ", error) time.sleep(2)my_posts = [ {"title": "title of post 1", "content": "content of post 1", "id": 1}, {"title": "favorite foods", "content": "I like pizza", "id": 2}]def find_post(id): for p in my_posts: if p['id'] == id: return pdef find_index_post(id): for i, p in enumerate(my_posts): if p['id'] == id: return i# Include the router objects for user and post endpointsapp.include_router(post.router)app.include_router(user.router)@app.get("/")def root(): return {"message": "Hello World"}
With these changes, FastAPI delegates request handling to the appropriate router based on the URL endpoints, keeping the code modular and manageable as the application grows.
Once refactored, test your application to confirm that all functionality operates as expected. Typical tests include:
Fetching all posts
Creating a new post
Retrieving a single post by ID
Deleting or updating posts
Creating a new user and retrieving the user by ID
For instance, a successful post creation might respond with:
Copy
Ask AI
{ "title": "this is the new title", "content": "this is the new content", "published": true, "id": 1, "created_at": "2021-08-22T01:35:58.101063-04:00"}