Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 25

Building a 'Create' Endpoint

Recommended resources

Hello! Welcome to the next lesson in our FastAPI course.

In the previous module, we laid the groundwork for database interaction by setting up SQLAlchemy, defining our models and schemas, and creating endpoints to read data (list and retrieve). Now, we're ready to start modifying our data.

This lesson focuses on the "C" in CRUD: Create. You will learn how to build a POST endpoint that accepts JSON data from a client, validates it, and uses it to create a new record in your database. This is a fundamental operation for any web application, from creating a new user account to logging the results of an AI model's prediction.

The Anatomy of a "Create" Endpoint

Before we write the code, let's establish the key components of an endpoint designed for creating new resources.

  1. HTTP Method: We use the POST method. Conventionally, POST is used to create a new resource within a collection (e.g., create a new item in the /items collection). The server is responsible for generating the unique identifier (like a primary key id) for the new resource.

  2. Request Body: The data for the new resource is sent in the request's body, typically as a JSON object. In FastAPI, we use a Pydantic model—what we've been calling a "create schema" (e.g., ItemCreate)—to define the expected structure and automatically validate this incoming data.

  3. Status Code: A successful creation should return an HTTP 201 Created status code. This is more specific than the default 200 OK and clearly communicates that a new resource has been created as a result of the request.

  4. Response Body: It's a common and helpful practice to return the full representation of the newly created resource in the response body. This allows the client to immediately get the server-generated data, most importantly the new id.


Implementing the Create Endpoint

Let's translate these concepts into code. We will continue building upon the application structure from the previous module, where we already have our database session dependency (get_db), SQLAlchemy models (e.g., models.Item), and Pydantic schemas (e.g., schemas.ItemCreate and schemas.Item).

The process of creating a new record involves four main steps inside our path operation function:

  1. Create an instance of your SQLAlchemy model using the data from the request body.
  2. Add that instance to the database session.
  3. Commit the transaction to save the record to the database.
  4. Refresh the instance to fetch the newly created data (like the auto-generated id) from the database.

The following short video provides a clear, step-by-step demonstration of this entire process. While the video uses a synchronous session for simplicity, the core logic (add, commit, refresh) is identical. We will adapt it for our asynchronous setup.

How to Build a FastAPI SQLite REST API in Python | CRUD Operations | SQLAlchemy | in Just 14 Minutes

Please watch this section from the video by Tisfoulla Academy. It demonstrates exactly how to build a POST endpoint to create a new user, which is the same pattern we will follow.

Watch the segment from 06:09 to 08:41. Pay close attention to how the Pydantic model is used to receive data, how the SQLAlchemy model instance is created, and the sequence of db.add(), db.commit(), and db.refresh().

Now, let's write the code for our create_item endpoint, adapting the pattern from the video to our asynchronous setup.

Here is the complete path operation for creating a new item:




# In your main.py or relevant router file

from fastapi import Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession

import models
import schemas
from database import get_db




# ... (your existing app setup and read endpoints) ...

@app.post("/items/", response_model=schemas.Item, status_code=status.HTTP_201_CREATED)
async def create_item(item: schemas.ItemCreate, db: AsyncSession = Depends(get_db)):
    """
    Create a new item in the database.
    """



    # Create a SQLAlchemy model instance from the request body data
    db_item = models.Item(**item.dict())
    



    # Add the new ORM object to the session
    db.add(db_item)
    



    # Commit the transaction to the database
    await db.commit()
    



    # Refresh the object to get the new data from the database, like the ID
    await db.refresh(db_item)
    
    return db_item

Let's break down the key lines:

  • @app.post("/items/", response_model=schemas.Item, status_code=status.HTTP_201_CREATED):

    • @app.post("/items/"): Registers this function to handle POST requests to the /items/ path.
    • response_model=schemas.Item: Tells FastAPI to serialize the return value using our Item schema, ensuring the response includes the id.
    • status_code=status.HTTP_201_CREATED: Sets the default success status code to 201.
  • async def create_item(item: schemas.ItemCreate, db: AsyncSession = Depends(get_db)):

    • item: schemas.ItemCreate: FastAPI validates the request body against this Pydantic model. The validated data is available in the item variable.
    • db: AsyncSession = Depends(get_db): Our familiar dependency that provides an asynchronous database session.
  • db_item = models.Item(**item.dict()): This is an elegant way to create the SQLAlchemy model instance. item.dict() converts the Pydantic model to a dictionary, and the ** operator unpacks it into keyword arguments that match the attributes of our models.Item class.

  • db.add(db_item): Stages the new db_item object to be inserted into the database.

  • await db.commit(): Persists all pending changes within the current transaction (in this case, our new item) to the database. Since our database driver is asynchronous, this is an await-able operation.

  • await db.refresh(db_item): This is a crucial step. After committing, the db_item object in our code doesn't yet have the id that the database just generated. db.refresh() updates the object with the data from the database, populating the id and any other server-side defaults.


Testing with the Interactive Docs

With the code in place, run your application with Uvicorn and navigate to /docs. You will see your new POST /items/ endpoint.

  1. Expand the endpoint. You'll see an "Example Value" for the request body, generated from your schemas.ItemCreate model.
  2. Click "Try it out".
  3. Modify the JSON in the "Request body" text area.
  4. Click "Execute".

You should receive a 201 Created response, and the response body will contain the JSON for your new item, complete with its database-generated id.

Fast API Swagger UI: Create Item POST Endpoint Schema
This image shows how FastAPI's interactive documentation provides a user interface to construct the JSON request body for a `POST` endpoint, based on the Pydantic schema you define.

For a more detailed walkthrough of the implementation and refactoring process, including separating Pydantic schemas and using session.refresh(), the following article is an excellent read.

Building A Simple CRUD Application With FastAPI

The "Create" and "Declaring Response Models" sections of this GormAnalysis article provide a great text-based explanation of the concepts we've just implemented.

Please read the section titled Create (under CRUD Operations) and the subsection titled Declaring Response Models. The code uses a synchronous session, but focus on the logic: defining a Pydantic model for the request, creating a SQLAlchemy instance, and the explanation of why session.refresh() is needed to return the created object.


Conclusion

In this lesson, you have successfully implemented the create functionality for your API. You now have a POST endpoint that can receive, validate, and persist new data. This is a massive step forward, transforming your API from a read-only service to a dynamic application that can manage data.

Key Takeaways:

  • POST requests are used to create new resources.
  • The request body, validated by a Pydantic "create" schema, contains the data for the new resource.
  • The HTTP 201 Created status code signals a successful creation.
  • The core database logic involves db.add(), await db.commit(), and await db.refresh().
  • await db.refresh() is essential for retrieving database-generated values like the primary key id before returning the object to the client.

In the next lesson, we will complete our set of CRUD operations by implementing the update and delete functionalities, giving you full control over your data lifecycle through the API.