🛠 Enhancing auth.py — Creating a Real User Endpoint
In this lesson, we move from a placeholder authentication route to building the foundation of a real user creation system.
Up until now, our routers/auth.py looked like this:
from fastapi import APIRouter
router = APIRouter()
@router.get("/auth/")
async def get_user():
return {"user": "authenticated"}This endpoint simply returned static data.
Now we’re going to build something meaningful.
1️⃣ Update Existing endpoint
Creating a resource in a REST API must use POST, not GET.
So we change this:
🗂️ routers/auth.py
@router.get("/auth/")To this:
🗂️ routers/auth.py
@router.post("/auth/")And we rename the function to reflect its responsibility:
🗂️ routers/auth.py
@router.post("/auth/")
async def create_user():
return {"user": "authenticated"}
We are now creating a user → therefore we use POST.
2️⃣ Introduce Request Validation
Before writing any logic, we define what data we expect from the client.
In backend development:
Never trust raw input.
We use Pydantic to validate incoming request data.
🗂️ routers/auth.py
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter()
class CreateUserRequest(BaseModel):
username: str
email: str
first_name: str
last_name: str
password: str
role: str3️⃣ Accept the Request Object
Now we pass this model into our route:
🗂️ routers/auth.py
@router.post("/auth/")
async def create_user(create_user_request: CreateUserRequest):
return {"message": "User received"}FastAPI now:
Receives JSON
Validates it
Converts it into a
CreateUserRequestobjectInjects it into the function
This is dependency injection and data parsing handled automatically.
4️⃣ Connect to Our Database Model
Inside models.py, we previously defined:
🗂️ models.py
class Users(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True)
username = Column(String, unique=True)
first_name = Column(String)
last_name = Column(String)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
role = Column(String)Notice something critical:
Our reuest model has:
passwordclass CreateUserRequest(BaseModel):
username: str
email: str
first_name: str
last_name: str
password: str
role: strBut our database model has:
hashed_passwordclass Users(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True)
username = Column(String, unique=True)
first_name = Column(String)
last_name = Column(String)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
role = Column(String)This is intentional.
We never store plain passwords in a database.
5️⃣ Why *model_dump() Will Fail
You might try:
create_user_model = Users(**create_user_request.model_dump())This will fail.
Why?
Because:
CreateUserRequestcontainspasswordUsersexpectshashed_password
They do not match.
This teaches an important backend concept:
API schemas and ORM models should not always be identical.
They represent different layers of the application.
6️⃣ Manual Field Mapping (Transformation Layer)
Instead, we map fields explicitly:
🗂️ routers/auth.py
from models import Users
@router.post("/auth/")
async def create_user(create_user_request: CreateUserRequest):
create_user_model = Users(
email=create_user_request.email,
username=create_user_request.username,
first_name=create_user_request.first_name,
last_name=create_user_request.last_name,
role=create_user_request.role,
hashed_password=create_user_request.password, # temporary
is_active=True
)
return create_user_modelImportant:
We are NOT hashing yet
We are NOT saving to the database yet
We are only creating an ORM instance
This creates a SQLAlchemy object in memory.
It does NOT persist anything.
Understanding that distinction is critical in backend development.
7️⃣ Testing the Endpoint
Run your application:
terminal
uvicorn main:app --reloadGo to:
/docsYou will now see:
POST
/auth/
Send demo data.
You’ll receive:
Status code: 200
User object returned
This proves:
Validation works
Object creation works
Field mapping works
But we are not production-ready yet.
🔐 Important Security Note
Right now, we are saving passwords as plain text.
That is unsafe.
In the next section, we will:
Hash passwords
Prevent raw password storage
Move toward real authentication
Begin persisting users into the database
We are building this correctly — layer by layer.
🗂️ routers/auth.py
from fastapi import APIRouter
from models import Users
from pydantic import BaseModel
router = APIRouter()
class CreateUserRequest(BaseModel):
username: str
email: str
first_name: str
last_name: str
password: str
role: str
@router.post("/auth/")
async def create_user(create_user_request: CreateUserRequest):
create_user_model = Users(
email=create_user_request.email,
username=create_user_request.username,
first_name=create_user_request.first_name,
last_name=create_user_request.last_name,
role=create_user_request.role,
hashed_password=create_user_request.password, # temporary
is_active=True
)
return create_user_modelNo Changes in other files or folder structure