Creating a Model Object FastAPI | SQLModel

3 min read

In the previous tutorial we created a user in the database. In this one we are going to create a blog post. We will be starting with creating a Pydantic or SQLModel Schema. I am going to choose pydantic, because pydantic is more versatile. Pydantic is not strongly coupled to SQLAlchemy ORM. So, in your company if you are using some other ORM, then also there won't be any problem.

Let's create schemas/blog.py:

from pydantic import BaseModel, Field, model_validator

class CreateBlog(BaseModel):
    title: str = Field(max_length=200)
    slug: str = Field(max_length=200)
    content: str

    @model_validator(mode="before")
    @classmethod
    def generate_slug_from_title(cls, data: dict) -> dict:
        if "title" in data:
            data["slug"] = data["title"].lower().replace(" ", "-")
        return data

The magic happens in the model_validator. With mode="before", it runs before Pydantic validates the fields. So when someone creates a blog post with just a title and content, the validator automatically generates the slug. If you want to test it's capabilities, you can simply test it even in the terminal.

C:\fastapi\blog> docker-compose exec -it web /bin/bash
root@f035c2e69d12:/app# python 
>>> from schemas.blog import CreateBlog
>>> CreateBlog(title="Hello World", content="This is some content")
CreateBlog(title='Hello World', slug='hello-world', content='This is some content')

Similarily we can add a schema to design how the API response will look like. 

from typing import Optional
from pydantic import BaseModel, Field, model_validator
from .user import ShowUser


class CreateBlog(BaseModel):
    title: str = Field(max_length=200)
    slug: str = Field(max_length=200)
    content: str

    @model_validator(mode="before")
    @classmethod
    def generate_slug_from_title(cls, data: dict) -> dict:
        if "title" in data:
            data["slug"] = data["title"].lower().replace(" ", "-")
        return data


class ShowBlog(BaseModel):
    id: int
    title: str
    slug: str
    content: str
    is_active: bool
    user_id: int

Time to focus on our crud service layer. Create a new file database/crud/blog.py:

from sqlmodel import Session
from fastapi import Depends

from database.models.blog import Blog
from apis.deps import get_db
from schemas.blog import CreateBlog


def insert_blog(blog: CreateBlog, db: Session) -> Blog:
    db_blog = Blog(
        title=blog.title,
        slug=blog.slug,
        content=blog.content,
        is_active=True,
        user_id=1
    )
    db.add(db_blog)
    db.commit()
    db.refresh(db_blog)
    return db_blog

This function is doing the heavy lifting. We're taking our validated Pydantic model and converting it to a SQLModel database object. Notice how we're hardcoding user_id=1 - yeah, that's a temporary thing. In production, you'd get this from the authenticated user. But hey, we're keeping it simple for now!

The db.refresh(db_blog) line is important - it updates our object with any database-generated values like the ID. Without it, you might get stale blog object.

from sqlmodel import Session
from fastapi import APIRouter, Depends, status

from apis.deps import get_db
from database.crud.blog import insert_blog
from schemas.blog import CreateBlog, ShowBlog


router = APIRouter()


@router.post("/blogs", response_model=ShowBlog, status_code=status.HTTP_201_CREATED)
def create_blog(blog: CreateBlog, db: Session = Depends(get_db)):
    return insert_blog(blog, db)

This is beautiful in its simplicity! One decorator, one function, and boom - you've got a REST API endpoint. The response_model=ShowBlog tells FastAPI exactly what to return, and it'll automatically handle the serialization for you.

That status.HTTP_201_CREATED is the proper HTTP status code for "Hey, I created something new!" Instead of the default 200. It's these little details that make your API feel professional.

Finally, we can focus on apis/main.py file which has our pre-configured api router.

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

api_router = APIRouter()


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

This is how you avoid ending up with a 500-line main.py at the root directory, that nobody wants to touch! Each feature gets its own router, and we organize them with prefixes and tags. The tags are super useful - they group your endpoints in the automatic API documentation at /docs.

The beauty of this setup is separation of concerns. Your Pydantic models handle validation, your CRUD functions handle database operations, and your routers handle HTTP stuff. When something breaks (and it will!), you know exactly where to look. 

Trying it out:

Now, every part is ready, we can start our docker compose server and visit: http://127.0.0.1:8002/docs

 

If you want to include the related foreignkey object simply specify it in the ShowBlog pydantic schema. e.g. 

user: Optional[ShowUser] = None

 

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.