User Creation Route

4 min read

Directory structure:

🌳 C:\fastapi\blog
├── 📁 alembic
|  ├── 📄 env.py
|  ├── 📄 README
|  ├── 📄 script.py.mako
|  └── 📁 versions
|     └── 📄 cedf4fedd4fe_user_and_blog_model.py
├── 📄 alembic.ini
├── 📁 apis
|  ├── 📄 deps.py
|  ├── 📄 main.py
|  └── 📁 v1
|     └── 📄 user.py
├── 📁 core
|  └── 📄 config.py
├── 📁 database
|  ├── 📁 crud
|  |  └── 📄 user.py
|  ├── 📄 db.py
|  └── 📁 models
|     ├── 📄 base.py
|     ├── 📄 blog.py
|     ├── 📄 user.py
|     └── 📄 __init__.py
├── 📄 docker-compose.yaml
├── 📄 Dockerfile
├── 📄 main.py
├── 📄 requirements.txt
└── 📁 schemas
   ├── 📄 user.py

Easy Right ?

When I did know basic fastapi, I used to do user registration like this.

@app.post("/register")
def create_user(email: str, password: str, db: Session = Depends(get_db)):
    # Insert directly into database
    db.execute(f"INSERT INTO users (email, password) VALUES ('{email}', '{password}')")
    return {"message": "User created!"}

And there is nothing wrong with it. We all begin like this, however, when our product evolves, we start to see issues. SQL injection vulnerabilities 💉, no data validation, passwords stored in plain text, no error handling - basically every security nightmare you can imagine rolled into 5 lines of code.

We already have the database and schemas ready. Now we need to build a strong architecture to design the APIs, which is maintainable for long term.

The CRUD Layer: Keep It Clean

Here's where things get interesting. Instead of cluttering our API endpoints with database logic, we create a separate CRUD layer. This keeps things organized and makes testing way easier. Let's create a new file database/crud/user.py

from sqlmodel import Session
from fastapi import Depends
from database.models.user import User

from apis.deps import get_db
from schemas.user import UserCreate


def insert_user(user: UserCreate, db: Session = Depends(get_db)) -> User:
    db_user = User(
        username=user.email,
        email=user.email,
        full_name=user.full_name,
        password=user.password,
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

Its still not sufficient but its a better start. We will talk about SQL Injection and Password Hashing in the Security section later. 

The API Endpoint: Simple Yet Robust

Let us now create the actual endpoint or route for user registration. Since we are going to store something to the databse we should use a POST request. Let's create a file apis/v1/user.py

from sqlmodel import Session
from fastapi import APIRouter, Depends, HTTPException, status
from apis.deps import get_db
from database.crud.user import insert_user
from schemas.user import UserCreate
from database.models.user import User

router = APIRouter()


@router.post("/users", response_model=User, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate, db: Session = Depends(get_db)) -> User:
    return insert_user(user, db)

Clean, right? The endpoint focuses on HTTP concerns while the CRUD layer handles database operations. Separation of concerns at its finest! 

Don't forget to include your router in the main application. However, instead of registering every single api in the main.py file at root directory. We are going to create a middleman main.py file inside the apis directory. This way we will not clutter the root main.py.  apis/main.py

from fastapi import APIRouter
from apis.v1.user import router as user_router

api_router = APIRouter()
api_router.include_router(user_router, prefix="/api/v1", tags=["users"])

And in your main app:

# main.py
from fastapi import FastAPI
from apis.main import api_router

app = FastAPI(title="My Awesome API")
app.include_router(api_router)

Let's got the the automated swagger docs and test our work. If everything's set up correctly, you'll get back a response like this.

You might notice a problem. We are displaying the password in the API response which is not a good thing. We can utilize either Pydantic or SQLModel to control the response format. Let me demonstrate how we can utilize pydantic to hide the password info and only show the relevant fields.

from pydantic import BaseModel, Field, field_validator
from sqlmodel import SQLModel

class UserCreate(BaseModel):
    #old
    

class ShowUser(BaseModel):
    id: int
    full_name: str
    email: str

## or 
class ShowUser(SQLModel):
    id: int
    full_name: str
    email: str

and in the api just tell the response model. 

from sqlmodel import Session
from fastapi import APIRouter, Depends, status
from apis.deps import get_db
from database.crud.user import insert_user
from schemas.user import UserCreate
from schemas.user import ShowUser

router = APIRouter()


@router.post("/users", response_model=ShowUser, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    return insert_user(user, db)

Now let's try the API again.

Todo:

This API still needs a ton of work. I compromised these things for the sake of simplicity.

  1. Always hash passwords - Never, ever store plain text passwords. Use bcrypt or similar.
  2. Handle duplicates gracefully - Users will try to register twice. Plan for it.
  3. Error handling matters - Give users meaningful error messages, not stack traces.
  4. Emailing - We should send an email or verify the authenticity of the user.

The code I've shown you here is not production-ready There is a long way to go, so let's stick with this series and we will slowly learn to build a production grade API.


P.S. - If you're wondering about password complexity validation, email verification, or user roles, those are great topics for follow-up posts. Let me know what you'd like to see next!

FastAPITutorial

My priority is to help build a production-ready application using FastAPI

I prioritize quality over simplicity. Our challenging approach ensures you're prepared for real-world development.

Contacts

Refunds:

Refund Policy
Social

Follow us on our social media channels to stay updated with our latest tutorials and resources.

© Copyright 2022-2025 Team FastAPITutorial. All rights reserved.