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.
-
Create a new directory for our project and navigate to it in the terminal.
mkdir fastapi_crud cd fastapi_crud
-
Create a virtual environment and activate it.
python3 -m venv venv source venv/bin/activate
-
Install FastAPI and other required libraries.
pip install fastapi uvicorn sqlalchemy databases[sqlite] sqlmodel
-
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
-
Next, let’s define our database models using SQLModel. We will create a simple
User
model withid
,name
, andemail
fields.class User(SQLModel, table=True): id: int = Field(default=None, primary_key=True) name: str email: str
- 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
- 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.
Has anyone used this in production?
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.
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!
@BugBytes can we see an example of GET route returning results from two joined tables?
I can not find the github repo. Could you share the link please?
you are the best
Another perfect video. Thanks a lot for providing them, really appreciated. Looking forward to see more content with the time.
Thanks for this series of videos. Really appreciate the clear explanations and references back to the documentation.
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.
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?
Thank you so much!
Would HTMX be something that would be easy to implement as a front end with FastAPI?
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
i just leave a comment here to help the channel with the promotion =)
Great video… Is there any difference when using async functions?
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.
thank you for sharing this.
Great, Learning a lot