Movatterモバイル変換


[0]ホーム

URL:


CodevoWeb

PressESC to close

CRUD RESTful API Server with Python, FastAPI, and PostgreSQL

6Comments184

This article will teach you how to create a CRUD RESTful API with Python, FastAPI,SQLAlchemy ORM,Pydantic,Alembic, PostgreSQL, and Docker-compose to perform the basicCreate/Read/Update/Delete operations against a database.

API with Python, FastAPI, SQLAlchemy ORM, Alembic, and PostgreSQL:

  1. RESTful API with Python & FastAPI: Access and Refresh Tokens
  2. RESTful API with Python & FastAPI: Send HTML Emails
  3. CRUD RESTful API Server with Python, FastAPI, and PostgreSQL

Related articles:

CRUD RESTful API Server with Python, FastAPI, and PostgreSQL

Python, FastAPI, PostgreSQL CRUD API Overview

We’ll build a CRUD RESTful API with Python and FastAPI to performCreate/Read/Update/Delete operations against a PostgreSQL database.

You canimport the Postman collection I used in testing the API into your Postman to make your life easier.

RESOURCEHTTP METHODROUTEDESCRIPTION
postsGET/api/postsFetch all posts
postsPOST/api/postsCreate a new post
postsGET/api/posts/:postIdRequest a single post
postsPUT/api/posts/:postIdUpdate a post
postsDELETE/api/posts/:postIdRemove a post

-You can create a new post in the database by making aPOST request to the/api/posts endpoint with the necessary data.

python, fastapi, postgresql, pydantic, and sqlalchemy crud restful api create new post

-To update a post in the database, you need to make aPUT request to the/api/posts/:postId endpoint with the edited data.

python, fastapi, postgresql, pydantic, and sqlalchemy crud restful api update a post

-You can request a single post from the database by making aGET request to the/api/posts/:postId endpoint.

python, fastapi, postgresql, pydantic, and sqlalchemy crud restful api get a single post

-You can also fetch all the posts from the database or a paginated list of the posts by making aGET request to the/api/posts?page=1&limit=10 endpoint.

python, fastapi, postgresql, pydantic, and sqlalchemy crud restful api fetch all posts

-Finally, you can also delete a post in the database by making aDELETE request to the/api/posts/:postId endpoint.

python, fastapi, postgresql, pydantic, and sqlalchemy crud restful api delete a post

-You can open the pgAdmin application to see all the posts you created.

python, fastapi, postgresql, pydantic, and sqlalchemy crud restful api check pgadmin to see the data

Prerequisites

Before you start this tutorial:

  • Basic knowledge of Python, and FastAPI will be helpful
  • Intermediate understanding of SQLAlchemy, and how ORMs work will be highly beneficial.
  • Have Docker and Python+3.6 installed

Setting up FastAPI and PostgreSQL

By default, the FastAPI framework supports both NoSQL and SQL databases, making it a great choice for developers.

The code we are going to write can easily be adapted to utilize any database supported by SQLAlchemy, like:

  • PostgreSQL
  • MySQL
  • MS-SQL
  • Firebird
  • SQLite
  • Oracle, etc.

When it comes to working with the PostgreSQL database, the easiest way to run the Postgres server on your system is by usingDocker andDocker-compose. At this point, you need to make sure you already haveDocker installed on your machine.

I going to useVS Code (Visual Studio Code) as my text editor but feel free to use any IDE or text editor of your choice.

Let’s start by creating a new project calledpython_fastapi to contain the FastAPI project:

$ mkdir python_fastapi$ cd python_fastapi$ code . # opens the project with VS Code

Open the integrated terminal in your text editor or IDE and run this command to create a virtual environment:

Windows Machine:

$ py -3 -m venv venv

macOS Machine:

$ python3 -m venv venv

Now createdocker-compose.yml file in the root project and add the following configurations:

docker-compose.yml

version: '3'services:  postgres:    image: postgres    container_name: postgres    ports:      - '6500:5432'    restart: always    env_file:      - ./.env    volumes:      - postgres-db:/var/lib/postgresql/datavolumes:  postgres-db:

Next, create a.env file in the root project and add the following credentials. These credentials will be used by the Postgres Docker image to configure the PostgreSQL database.

.env

DATABASE_PORT=6500POSTGRES_PASSWORD=password123POSTGRES_USER=postgresPOSTGRES_DB=fastapiPOSTGRES_HOST=postgresPOSTGRES_HOSTNAME=127.0.0.1

.gitignore

__pycache__venv/.env

Start the PostgreSQL Docker container with this command:

$ docker-compose up -d

You can stop the container with this command:

$ docker-compose down

Building the FastAPI Server

First and foremost, We need to activate the virtual environment we created. To do that, create aapp/main.py file for VS Code to prepare the Python development environment.

Next, close and reopen the integrated terminal for VS Code to automatically activate the virtual environment.

Also, let’s turn the app folder into a Python package by creating an emptyapp/__init__.py file.

Install the FastAPI library and its peer dependencies:

pip install fastapi[all]

Starting the FastAPI Server

Open theapp/main.py file and add the following code to help us initialize the FastAPI server.

from fastapi import FastAPIapp = FastAPI()@app.get('/api/healthchecker')def root():    return {'message': 'Hello World'}

Start the FastAPI server with this command:

uvicorn app.main:app --host localhost --port 8000 --reload

Let’s evaluate the above command:

  • uvicorn –  a high-performanceASGI server for python applications.
  • app.main: the path to theapp/main.py file
  • app.main:app : the object returned by evokingFASTAPI()
  • --host : specifies the hostname
  • --port : specifies the port. Default is8000.
  • --reload: auto-reload the server upon every file change

Open any API testing tool of your choice and make aGET request tohttp://localhost:8000/api/healthchecker . You should get theHello World message we returned from the path operation function.

testing the fastapi with postman

Setting up Environment Variables

Out-of-the-box,Pydantic has a built-in tool for reading, loading, and validating the environment variables specified in a configuration file.

All we have to do is to create a custom class model that inherits theBaseSettings class provided byPydantic.

Replace the content of the.env file with the following environment variables:

.env

DATABASE_PORT=6500POSTGRES_PASSWORD=password123POSTGRES_USER=postgresPOSTGRES_DB=fastapiPOSTGRES_HOST=postgresPOSTGRES_HOSTNAME=127.0.0.1ACCESS_TOKEN_EXPIRES_IN=15REFRESH_TOKEN_EXPIRES_IN=60JWT_ALGORITHM=RS256CLIENT_ORIGIN=http://localhost:3000JWT_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVmlQWlJyVFpjd3l4RVhVRGpNaFZuCi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUUpBYm5LaENOQ0dOSFZGaHJPQ0RCU0IKdmZ2ckRWUzVpZXAwd2h2SGlBUEdjeWV6bjd0U2RweUZ0NEU0QTNXT3VQOXhqenNjTFZyb1pzRmVMUWlqT1JhUwp3UUloQU84MWl2b21iVGhjRkltTFZPbU16Vk52TGxWTW02WE5iS3B4bGh4TlpUTmhBaUVBbWRISlpGM3haWFE0Cm15QnNCeEhLQ3JqOTF6bVFxU0E4bHUvT1ZNTDNSak1DSVFEbDJxOUdtN0lMbS85b0EyaCtXdnZabGxZUlJPR3oKT21lV2lEclR5MUxaUVFJZ2ZGYUlaUWxMU0tkWjJvdXF4MHdwOWVEejBEWklLVzVWaSt6czdMZHRDdUVDSUVGYwo3d21VZ3pPblpzbnU1clBsTDJjZldLTGhFbWwrUVFzOCtkMFBGdXlnCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tJWT_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVgppUFpSclRaY3d5eEVYVURqTWhWbi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==

Next, we need to configure the custom class to know the path to the configuration file. This will ensure that the model class initializer reads the content of the environment variables file if we do not initialize the class attributes.

Now create aapp/config.py file and add the custom class modelSettings :

app/config.py

from pydantic import BaseSettingsclass Settings(BaseSettings):    DATABASE_PORT: int    POSTGRES_PASSWORD: str    POSTGRES_USER: str    POSTGRES_DB: str    POSTGRES_HOST: str    POSTGRES_HOSTNAME: str    JWT_PUBLIC_KEY: str    JWT_PRIVATE_KEY: str    REFRESH_TOKEN_EXPIRES_IN: int    ACCESS_TOKEN_EXPIRES_IN: int    JWT_ALGORITHM: str    CLIENT_ORIGIN: str    class Config:        env_file = './.env'settings = Settings()

Connecting to the PostgreSQL Server

There are numerous libraries we can use to interact with the PostgreSQL server in a Python application. Most of the libraries depend on the native PostgreSQL driver to function properly.

There are a couple of PostgreSQL adapters butPsycopg is the most popular PostgreSQL database adapter for Python programming language.

Below are the features of a good PostgreSQL driver:

By default,SQLAlchemy usespsycopg2 under the hood to interact with the PostgreSQL server.

pip install sqlalchemy psycopg2
  • SQLAlchemy – a popular Python ORM (object-relational mapper)
  • Psycopg – a popular PostgreSQL driver

Now let’s create the utility functions to connect and discount the PostgreSQL server. Create aapp/database.py file and add the following code:

app/database.py

from sqlalchemy import create_enginefrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmakerfrom .config import settingsSQLALCHEMY_DATABASE_URL = f"postgresql://{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_HOSTNAME}:{settings.DATABASE_PORT}/{settings.POSTGRES_DB}"engine = create_engine(    SQLALCHEMY_DATABASE_URL)SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)Base = declarative_base()def get_db():    db = SessionLocal()    try:        yield db    finally:        db.close()

Installing the UUID OSSP PostgreSQL Plugin

By default, PostgreSQL has native support for theUUID data type, however, since we want to use it as a default value for theID column, we need to install theUUID OSSP module plugin for it to work.

To install theUUID OSSP module extension, we first need to access the bash shell of the running PostgreSQL Docker container withdocker exec -it <container name> bash and run theCREATE EXTENSION command to install it.

docker exec -it postgres bash

Once you have access to the bash shell, we can use execute any PostgreSQL commands to communicate with the Postgres server.

Now use the following steps to install theUUID plugin:

Step 1: Log into the running Postgres database with this commandpsql -U <database username> <database name>:

psql -U admin fastapi
  • admin – is the database username provided in the.env file
  • fastapi – is the database name provided in the.env file

Step 2: You can display all the available extensions with this command

select * from pg_available_extensions;
postgresql extensions

After displaying the available extensions, you will notice theuuid-ossp module plugin is available but not installed.

You can hit the Enter key multiple times to scroll down the list and pressq to exit.

Step 3: Now install theuuid-ossp module with this command

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

After installing theUUID OSSP module, use\q to exit the Postgres server shell andexit to exit the Docker container bash shell.

How to Create Database Models with SQLAlchemy

We already coveredRESTful API with Python & FastAPI: Access and Refresh Tokens where we defined theusers table with SQLAlchemy, so I’ll skip the finer details. If you arrived here by Googling questions aboutHow to build RESTful APIs with FastAPI and Python, you should probably catch up on how to implement JWT authentication with FastAPI.

SQLAlchemy allows us to define module-level constructs to represent the structure of the data we will be fetching from the database.

We use Declarative Mapping to create both a Python object model anddatabase metadata to represent the real SQL tables in the database.

Now replace the content of theapp/models.py file with the following models:

app/models.py

import uuidfrom .database import Basefrom sqlalchemy import TIMESTAMP, Column, ForeignKey, String, Boolean, textfrom sqlalchemy.dialects.postgresql import UUIDfrom sqlalchemy.orm import relationshipclass User(Base):    __tablename__ = 'users'    id = Column(UUID(as_uuid=True), primary_key=True, nullable=False,                default=uuid.uuid4)    name = Column(String,  nullable=False)    email = Column(String, unique=True, nullable=False)    password = Column(String, nullable=False)    photo = Column(String, nullable=True)    verified = Column(Boolean, nullable=False, server_default='False')    verification_code = Column(String, nullable=True, unique=True)    role = Column(String, server_default='user', nullable=False)    created_at = Column(TIMESTAMP(timezone=True),                        nullable=False, server_default=text("now()"))    updated_at = Column(TIMESTAMP(timezone=True),                        nullable=False, server_default=text("now()"))class Post(Base):    __tablename__ = 'posts'    id = Column(UUID(as_uuid=True), primary_key=True, nullable=False,                default=uuid.uuid4)    user_id = Column(UUID(as_uuid=True), ForeignKey(        'users.id', ondelete='CASCADE'), nullable=False)    title = Column(String, nullable=False)    content = Column(String, nullable=False)    category = Column(String, nullable=False)    image = Column(String, nullable=False)    created_at = Column(TIMESTAMP(timezone=True),                        nullable=False, server_default=text("now()"))    updated_at = Column(TIMESTAMP(timezone=True),                        nullable=False, server_default=text("now()"))    user = relationship('User')

In layman’s terms, when we create a model with an ORM like SQLAlchemy, that translates to an SQL table in the database.

In the above code, we created two class models that inherited theBase class we exported in theapp/database.py file to help us create theusers andposts tables in the PostgreSQL database.

Also, we useduuid.uuid4 function as a default value for theID column. This will instruct PostgreSQL to generate a UUID for the ID column anytime a new record is added.

Creating Validation Schemas with Pydantic

We are now ready to create the validation schemas withPydantic to validate the requests and responses.

Replace the content of theapp/schemas.py file with the following schema definitions:

app/schemas.py

from datetime import datetimefrom typing import Listimport uuidfrom pydantic import BaseModel, EmailStr, constrclass UserBaseSchema(BaseModel):    name: str    email: EmailStr    photo: str    class Config:        orm_mode = Trueclass CreateUserSchema(UserBaseSchema):    password: constr(min_length=8)    passwordConfirm: str    role: str = 'user'    verified: bool = Falseclass LoginUserSchema(BaseModel):    email: EmailStr    password: constr(min_length=8)class UserResponse(UserBaseSchema):    id: uuid.UUID    created_at: datetime    updated_at: datetimeclass FilteredUserResponse(UserBaseSchema):    id: uuid.UUIDclass PostBaseSchema(BaseModel):    title: str    content: str    category: str    image: str    user_id: uuid.UUID | None = None    class Config:        orm_mode = Trueclass CreatePostSchema(PostBaseSchema):    passclass PostResponse(PostBaseSchema):    id: uuid.UUID    user: FilteredUserResponse    created_at: datetime    updated_at: datetimeclass UpdatePostSchema(BaseModel):    title: str    content: str    category: str    image: str    user_id: uuid.UUID | None = None    created_at: datetime | None = None    updated_at: datetime | None = None    class Config:        orm_mode = Trueclass ListPostResponse(BaseModel):    status: str    results: int    posts: List[PostResponse]

Creating the FastAPI Controllers

Now that we’ve defined the validation schemas and database model, let’s create the FastAPI path operation functions to perform the CRUD functionalities.

  • @router.get('/api/posts/') – This path operation function will retrieve a paginated list of post items from the database and return them to the client.
  • @router.post('/api/posts/') – This path operation function will add a new post item to the database.
  • @router.put('/api/notes/{id}') – This path operation function will update the fields of a post item in the database.
  • @router.get('/api/notes/{id}') – This path operation function will get a single post item from the database and return it to the client.
  • @router.delete('/api/notes/{id}') – This path operation function will delete a single post item in the database.

To begin, create a “routers” folder in the app directory. Then, create apost.py file in the “routers” folder and add these module imports:

app/routers/post.py

from datetime import datetimeimport uuidfrom .. import schemas, modelsfrom sqlalchemy.orm import Sessionfrom fastapi import Depends, HTTPException, status, APIRouter, Responsefrom ..database import get_dbfrom app.oauth2 import require_user

Fetch All Posts Handler

The first path operation function will perform theREAD functionality of CRUD. This route handler will be evoked to return a selected list of records. To avoid sending a huge JSON payload of all the records to the client, we’ll add a pagination feature where a user can select a range of records in the database. If the user doesn’t provide thelimit parameter in the URL, the route controller will only return the first10 records.

So, open theapp/routers/post.py file and add this block of code:

app/routers/post.py

# [...] importrouter = APIRouter()# [...] Get All Posts@router.get('/', response_model=schemas.ListPostResponse)def get_posts(db: Session = Depends(get_db), limit: int = 10, page: int = 1, search: str = '', user_id: str = Depends(require_user)):    skip = (page - 1) * limit    posts = db.query(models.Post).group_by(models.Post.id).filter(        models.Post.title.contains(search)).limit(limit).offset(skip).all()    return {'status': 'success', 'results': len(posts), 'posts': posts}

Above, we calculated the number of records to skip based on thepage andlimit parameters. Next, we created a query and appended all the relevant query methods before calling the.all() function to execute it.

Then, we returned the list of records retrieved from the database to the client in the JSON response.

Create New Post Handler

This path operation function will perform theCREATE functionality of CRUD. When aPOST request is made to the/api/notes/ endpoint, this route handler will be evoked to add the data to the database.

To validate the request body and send appropriate validation errors to the client, we’ll pass theCreatePostSchema as a dependency to the path operation function.

FastAPI will use theCreatePostSchema to deserialize the JSON payload, validate the data against the rules defined in the schema and assign the resulting Python object to thepost variable.

app/routers/post.py

# [...] importrouter = APIRouter()# [...] Get All Posts# [...] Create Post@router.post('/', status_code=status.HTTP_201_CREATED, response_model=schemas.PostResponse)def create_post(post: schemas.CreatePostSchema, db: Session = Depends(get_db), owner_id: str = Depends(require_user)):    post.user_id = uuid.UUID(owner_id)    new_post = models.Post(**post.dict())    db.add(new_post)    db.commit()    db.refresh(new_post)    return new_post

Therequire_user dependency will ensure that the user is authenticated before the actual request is processed. To add the new record to the database, we created an instance of thePost model using the data provided in the request body and called thedb.add() method to add the data to the current transaction.

Then, we called thedb.commit() method to save the data in the database. Once the data has been stored in the database, thedb.refresh() method will be called to update the fields of the model instance with the fields of the newly-created record.

Update Post Handler

Now that we are able to add new records, let’s create a path operation function to edit a single record in the database. This route controller will perform theUPDATE operation of CRUD.

To begin, we’ll construct a query to find the record by ID and assign the query to thepost_query variable. Then, we’ll execute the query to get the record that matches the provided ID. If no record was found, a404 error will be returned to the client.

app/routers/post.py

# [...] importrouter = APIRouter()# [...] Get All Posts# [...] Create Post# [...] Update Post@router.put('/{id}', response_model=schemas.PostResponse)def update_post(id: str, post: schemas.UpdatePostSchema, db: Session = Depends(get_db), user_id: str = Depends(require_user)):    post_query = db.query(models.Post).filter(models.Post.id == id)    updated_post = post_query.first()    if not updated_post:        raise HTTPException(status_code=status.HTTP_200_OK,                            detail=f'No post with this id: {id} found')    if updated_post.user_id != uuid.UUID(user_id):        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,                            detail='You are not allowed to perform this action')    post.user_id = user_id    post_query.update(post.dict(exclude_unset=True), synchronize_session=False)    db.commit()    return updated_post

Above, we called the.update() method on the current query to update the fields with the ones provided in the request body. Then, we called thedb.commit() method to save the changes to the database. Lastly, we returned the newly-updated record to the client in the JSON response.

Get a Single Post Handler

This path operation function will also perform aREAD functionality but it will only return a single record. Here, we’ll extract the ID parameter from the request URL and query the database to find the record that matches the current query.

To do this, we’ll construct a new query and append the.filter() method to select the record with the provided ID. After that, we’ll call the.first() method to execute the query and return the found record to the client.

app/routers/post.py

# [...] importrouter = APIRouter()# [...] Get All Posts# [...] Create Post# [...] Update Post# [...] Get a single post@router.get('/{id}', response_model=schemas.PostResponse)def get_post(id: str, db: Session = Depends(get_db), user_id: str = Depends(require_user)):    post = db.query(models.Post).filter(models.Post.id == id).first()    if not post:        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,                            detail=f"No post with this id: {id} found")    return post

Remove Post Handler

Finally, let’s create a path operation function to handleDELETE requests. This route handler will be called to delete a record in the database when a DELETE request is made to the/api/posts/{id} endpoint.

app/routers/post.py

# [...] importrouter = APIRouter()# [...] Get All Posts# [...] Create Post# [...] Update Post# [...] Get a single post# [...] Delete Post@router.delete('/{id}')def delete_post(id: str, db: Session = Depends(get_db), user_id: str = Depends(require_user)):    post_query = db.query(models.Post).filter(models.Post.id == id)    post = post_query.first()    if not post:        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,                            detail=f'No post with this id: {id} found')    if str(post.user_id) != user_id:        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,                            detail='You are not allowed to perform this action')    post_query.delete(synchronize_session=False)    db.commit()    return Response(status_code=status.HTTP_204_NO_CONTENT)

Let’s evaluate the above code. First, we created a query to find a record by the ID provided in the request URL. Then, we evoked thepost_query.first() method to retrieve the found record and assigned it to the “post” variable.

Next, we called thepost_query.delete() method to delete the record in the current transaction. After that, we called thedb.commit() method to permanently delete the record in the database.

Complete Code for the Post Handlers

app/routers/post.py

import uuidfrom .. import schemas, modelsfrom sqlalchemy.orm import Sessionfrom fastapi import Depends, HTTPException, status, APIRouter, Responsefrom ..database import get_dbfrom app.oauth2 import require_userrouter = APIRouter()@router.get('/', response_model=schemas.ListPostResponse)def get_posts(db: Session = Depends(get_db), limit: int = 10, page: int = 1, search: str = '', user_id: str = Depends(require_user)):    skip = (page - 1) * limit    posts = db.query(models.Post).group_by(models.Post.id).filter(        models.Post.title.contains(search)).limit(limit).offset(skip).all()    return {'status': 'success', 'results': len(posts), 'posts': posts}@router.post('/', status_code=status.HTTP_201_CREATED, response_model=schemas.PostResponse)def create_post(post: schemas.CreatePostSchema, db: Session = Depends(get_db), owner_id: str = Depends(require_user)):    post.user_id = uuid.UUID(owner_id)    new_post = models.Post(**post.dict())    db.add(new_post)    db.commit()    db.refresh(new_post)    return new_post@router.put('/{id}', response_model=schemas.PostResponse)def update_post(id: str, post: schemas.UpdatePostSchema, db: Session = Depends(get_db), user_id: str = Depends(require_user)):    post_query = db.query(models.Post).filter(models.Post.id == id)    updated_post = post_query.first()    if not updated_post:        raise HTTPException(status_code=status.HTTP_200_OK,                            detail=f'No post with this id: {id} found')    if updated_post.user_id != uuid.UUID(user_id):        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,                            detail='You are not allowed to perform this action')    post.user_id = user_id    post_query.update(post.dict(exclude_unset=True), synchronize_session=False)    db.commit()    return updated_post@router.get('/{id}', response_model=schemas.PostResponse)def get_post(id: str, db: Session = Depends(get_db), user_id: str = Depends(require_user)):    post = db.query(models.Post).filter(models.Post.id == id).first()    if not post:        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,                            detail=f"No post with this id: {id} found")    return post@router.delete('/{id}')def delete_post(id: str, db: Session = Depends(get_db), user_id: str = Depends(require_user)):    post_query = db.query(models.Post).filter(models.Post.id == id)    post = post_query.first()    if not post:        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,                            detail=f'No post with this id: {id} found')    if str(post.user_id) != user_id:        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,                            detail='You are not allowed to perform this action')    post_query.delete(synchronize_session=False)    db.commit()    return Response(status_code=status.HTTP_204_NO_CONTENT)

Adding the FastAPI Routers

At this point, we’ve implemented the fiveCRUD functionalities. Now let’s register the router in the app so that a client can interact with the path operation functions.

app/main.py

from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddlewarefrom app.config import settingsfrom app.routers import user, auth, postapp = FastAPI()origins = [    settings.CLIENT_ORIGIN,]app.add_middleware(    CORSMiddleware,    allow_origins=origins,    allow_credentials=True,    allow_methods=["*"],    allow_headers=["*"],)app.include_router(auth.router, tags=['Auth'], prefix='/api/auth')app.include_router(user.router, tags=['Users'], prefix='/api/users')app.include_router(post.router, tags=['Posts'], prefix='/api/posts')@app.get('/api/healthchecker')def root():    return {'message': 'Hello World'}

Conclusion

With this FastAPI, PostgreSQL, Pydantic, SQLAlchemy, Alembic, and Docker-compose example in Python, you’ve learned how to build a Restful CRUD API server to perform the basicCreate/Read/Update/Delete operations.

FastAPI and PostgreSQL CRUD API Source Code

You can get the completesource code in this GitHub repository

Share Article:

tRPC API with Postgres, Prisma & Node.js: JWT Authentication

Left Arrow

Build a Node.js gRPC Server and Client: Register and Login User

Right Arrow

6 Comments

  1. Đào Mạnh Linhon March 24, 2023

    hello friend, I am following your instructions on FastAPI following the path :
    https://codevoweb.com/crud-restful-api-server-with-python-fastapi-and-postgresql/
    but when I call api : create/posts I get the following error:
    {
    “loc”: [
    “body”,
    “user”
    ],
    “msg”: “instance of ObjectId expected”,
    “type”: “type_error.arbitrary_type”,
    “ctx”: {
    “expected_arbitrary_type”: “ObjectId”
    }
    }

    hope you can help me.
    thanks you very much!

    Reply
    • Edemon March 24, 2023

      It appears that you have made modifications to the code, but I’m a bit confused about the error you’re getting regardingObjectId.

      This term is typically associated with MongoDB, whereas our project uses PostgreSQL and Sqlalchemy.

      In order to assist you with this issue, I suggest that you upload your project to GitHub and send me the link through the contact form on the contact page. By doing so, I can examine your code and help identify where the problem is originating.

      Reply
      • Đào Mạnh Linhon March 24, 2023

        Thank you for your support, let me spend more time to review the code.
        thank you very much

        Reply
  2. Diego Ricardo De Leon Godoyon August 3, 2023

    i have an error in app/routers/post.py at the 6th line, in the app.oauth2

    Reply

Leave a ReplyCancel reply

This site is protected by reCAPTCHA and the GooglePrivacy Policy andTerms of Service apply.

This site uses Akismet to reduce spam.Learn how your comment data is processed.

Support Me!

paypal donate button

Recent posts

Categories

x

[8]ページ先頭

©2009-2025 Movatter.jp