Essential FastAPI Guide: Creating a CRUD API with SQLModel and Database Integration

Posted by


In this tutorial, we will be building a CRUD API using FastAPI with a database and SQLModel. FastAPI is a modern web framework for building APIs with Python, while SQLModel is a Python library that helps to define and interact with SQL databases using standard Python data structures.

For this tutorial, we will be using SQLite as our database, but you can easily switch to another database by changing the database URL in the connection string.

Let’s get started by setting up our environment and installing the necessary libraries.

  1. Create a new directory for our project and navigate to it in the terminal.

    mkdir fastapi_crud
    cd fastapi_crud
  2. Create a virtual environment and activate it.

    python3 -m venv venv
    source venv/bin/activate
  3. Install FastAPI and other required libraries.

    pip install fastapi uvicorn sqlalchemy databases[sqlite] sqlmodel
  4. Create a new file named main.py in your project directory and import the necessary libraries.

    from fastapi import FastAPI, HTTPException
    from fastapi.responses import JSONResponse
    from sqlalchemy import create_engine
    from databases import Database
    from sqlmodel import SQLModel, Field, Session, create_engine as db_create_engine
  5. Next, let’s define our database models using SQLModel. We will create a simple User model with id, name, and email fields.

    class User(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    name: str
    email: str
  6. Now, let’s set up our database connection using SQLAlchemy and databases.
    
    DATABASE_URL = "sqlite:///./test.db"

database = Database(DATABASE_URL)
engine = create_engine(DATABASE_URL)
session = Session(engine)


7. Let's create our FastAPI application and define our CRUD endpoints for the `User` model.
```python
app = FastAPI()

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()

@app.post("/users/", response_model=User)
async def create_user(user: User):
    query = User.insert().values(**user.dict())
    user.id = await database.execute(query)
    return user

@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
    query = User.select().where(User.id == user_id)
    user = await database.fetch_one(query)
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user

@app.put("/users/{user_id}", response_model=User)
async def update_user(user_id: int, user: User):
    query = User.update().where(User.id == user_id).values(**user.dict())
    await database.execute(query)
    updated_user = await database.fetch_one(User.select().where(User.id == user_id))
    if updated_user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return updated_user

@app.delete("/users/{user_id}", response_model=User)
async def delete_user(user_id: int):
    query = User.delete().where(User.id == user_id)
    deleted_user = await database.fetch_one(User.select().where(User.id == user_id))
    if deleted_user is None:
        raise HTTPException(status_code=404, detail="User not found")
    await database.execute(query)
    return deleted_user

@app.get("/users/", response_model=list[User])
async def list_users():
    query = User.select()
    users = await database.fetch_all(query)
    return users
  1. Finally, let’s start our FastAPI application using Uvicorn.
    uvicorn main:app --reload

You can now test your CRUD API by navigating to http://localhost:8000/docs in your browser. You should see the Swagger UI where you can interact with your API by creating, reading, updating, and deleting users.

That’s it! You have successfully built a CRUD API using FastAPI with a database and SQLModel. You can extend this example by adding more models, endpoints, and custom business logic to suit your specific requirements.

0 0 votes
Article Rating
18 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
@agb2557
1 month ago

Has anyone used this in production?

@_yurisales
1 month ago

I've got a problem with the datetime format when starting up the application, so I solved like this:

@asynccontextmanager
async def lifespan(app: FastAPI):
DATAFILE = pathlib.Path() / "data" / "tracks.json"

session = Session(engine)

stmt = select(TrackModel)
result = session.exec(stmt).first()

if result is None:
with open(DATAFILE, "r", encoding='utf-8') as f:
tracks = json.load(f)
for track in tracks:
track["last_play"] = datetime.strptime(
track["last_play"], "%Y-%m-%d %H:%M:%S"
)
session.add(TrackModel(**track))
session.commit()

session.close()
yield

app = FastAPI(lifespan=lifespan)

I'm using async context manager because on_event is deprecated.

@rippinsail
1 month ago

Thank you very much for another valuable tutorial!

I tried to run it with the latest versions of the packages and found out that there are issues with pydantic v2 in terms of datetime handling.
When I understood the documentation correct the string (e.g. "2024-02-07T11:01:29.036Z") should work at the post function (https://docs.pydantic.dev/2.0/usage/types/datetime/ -> str; the following formats are accepted: YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]).

But I had to modify the post like this to get it working:
@app.post('/tracks/', response_model=Track, status_code=201)
def create_track(track: TrackModel, session: Session = Depends(get_session)):
# explicit conversion to datetime object
track.last_play = datetime.strptime(track.last_play, "%Y-%m-%dT%H:%M:%S.%fZ")
session.add(track)
session.commit()
session.refresh(track)
return track

Is there an issue with SQLModel (Version 0.0.14 supports Pydantic v2) or did I unterstand something wrong?

What would be the proper way to handle datetime with Pydantic v2?

Would you recommend using SQLModel with FastAPI for a production project or better use SQLAlchemy?

Thank you very much in advance!

@IPADrinker
1 month ago

@BugBytes can we see an example of GET route returning results from two joined tables?

@OSCARCORTEZVILLCA
1 month ago

I can not find the github repo. Could you share the link please?

@vitorsilva-or1dj
1 month ago

you are the best

@Herdogan80
1 month ago

Another perfect video. Thanks a lot for providing them, really appreciated. Looking forward to see more content with the time.

@cutlervufamily
1 month ago

Thanks for this series of videos. Really appreciate the clear explanations and references back to the documentation.

@CarstenAevermann
1 month ago

In the current video about alpinejs you convert data from SQL to json for frontend filtering. Could you explain what's the best approach when such lists are huge? Is it still appropriate to filter in frontend or is there a better way? How to decide where the sort/filtering happens (frontend/alpinejs vs backend/fastAPI/sql ) thx and your explanations are a great help and didactically the best.

@GavinElie
1 month ago

Your videos are clear and to the point.👍Keep up the good work!! Does SQLModel simplify database integration as opposed to using SQLAlchemy directly?? In other words, does it produce cleaner code?

@nices2109
1 month ago

Thank you so much!

@kerwbstomp
1 month ago

Would HTMX be something that would be easy to implement as a front end with FastAPI?

@UsedYourName
1 month ago

ur very precise in ur explanations lol I really hope u get the recognition u deserve because the quality of the videos is mad fire dawg

@koshkinndom8049
1 month ago

i just leave a comment here to help the channel with the promotion =)

@fidelischukwunyere3142
1 month ago

Great video… Is there any difference when using async functions?

@seydinaoumarsamabaly1806
1 month ago

I started fastapi and stopped months ago. Now it's time to start over because it's explained by one of the best masters on the net. Thank you for all mate 🙌. Always a pleasure to wacth your videos.

@fortunebezetshali7468
1 month ago

thank you for sharing this.

@JustinSpryce
1 month ago

Great, Learning a lot