FastAPI is a new Python framework for developing web APIs

FastAPI is a new Python framework for developing web APIs that has gained popularity over the last few years.

One of the many great reasons to use Python is the vast amount of mature and stable libraries to choose from. For example, Django and Flask offer a great web development experience and troves of helpful documentation.

FastApi

Recently, the Python ecosystem has seen exciting developments powered by new features available only in Python 3+, such as coroutines and optional typing. This new era of libraries and frameworks promises both greater speed and ease of development, bringing Python on par with newer languages like Go and Rust while keeping the core experience that made Python so popular.

FastAPI is a new Python framework for developing web APIs that has gained popularity over the last few years. If you plan to use Python for web development, it will serve you well to become familiar with FastAPI.

In this tutorial, you'll build an API for a database of remote working locations using FastAPI. CodingNomads are always in need of good coffee and wifi while on the road. With this API, you can ask your friends from all over the world to submit their favorite places so that you'll always know the best place to go.

In this article you will:

  • Create a new FastAPI project from scratch.
  • Create an API for fellow CodingNomads to submit remote working locations.
  • Save the app's data to a real database using an ORM.
  • Get ready to build your tool to locate the best remote working locations for your fellow travelers, and learn to use the modern Python FastAPI library on the way.

Setting up the Project


To get started you will go through the usual Python project setup steps. By the end of this setup, you'll have a base project that can be re-used for other FastAPI projects.

First, create a new folder for your project. Then, create a new virtual environment inside it:

mkdir fastnomads
cd fastnomads
python3 -m venv env
    

This will ensure the Python packages we install stay isolated to the project.

Next, activate the virtualenv: 

source env/bin/activate 

Now you can install FastAPI and uvicorn, an ASGI server:

pip install fastapi uvicorn  
 
And now you should be ready to write some code.

Starting with “Hello World”


Before you delve into coffee shops and libraries, you should have the traditional “Hello World” app up and running in FastAPI. This allows you to prove that your initial setup is working properly.

Open up your favorite editor and paste the following code into a file called main.py. You'll use this file for the remainder of your development:

    from fastapi import FastAPI, Depends

    app = FastAPI()

    @app.get('/')
    async def root():
         return {'message': 'Hello World!'}
    

In just these five lines of code, you've already created a working API. If you've ever used Flask this should look very familiar. The last three lines are the most interesting.

This is a route:

@app.get('/') 
It tells FastAPI that the following method should be run when the user requests the "/" path.

This is a method declaration:

async def root(): 
 
Notice the async def: this method will be run as a Python3 coroutine! If you’d like to learn more about concurrency and async, FastAPI itself has a great explanation of the whole thing and what makes it so fast.

Finally, the return statement where we send the data to the browser:
 
return {'message': 'Hello World!'} 
As you might expect, visiting this endpoint will return a JSON response matching the dictionary above.

Enough talk, run the server to see it in action!

uvicorn main:app --reload 
Now try visiting http://127.0.0.1:8000 in your browser. You should see this:

{ "message": "Hello World!" }
Perfect, but that's not all! FastAPI also automatically generated fully interactive API documentation that you can use to interact with your new API. Visit http://127.0.0.1:8000/docs in your browser. You should see something like this:
fastapis

In this image you can see the endpoint that was just defined, and even execute it straight from your browser!

Since you are creating an API only with no frontend user interface, you'll be using the interactive documentation as the main method of interacting with the API.

Defining Models and Business Logic

Now it’s time to take your application beyond the basics and start writing code specific to your goal.

First you'll create a Pydantic model to represent a Place. Pydantic is a data validation library that uses some neat tricks from the Python3 type system. FastAPI relies heavily on it to both validate incoming data and serialize outgoing data.

You'll also define a route to create a new Place. For now all it will do is return the submitted Place back to you.

Add the following code so that your main.py looks like this:

    from fastapi import FastAPI, Depends
    from pydantic import BaseModel
    from typing import Optional, List
    
    app = FastAPI()
    
    class Place(BaseModel):
         name: str
         description: Optional[str] = None
         coffee: bool
         wifi: bool
         food: food
         lat: float
         lng: float
    
         class config:
             orm_mode = True
    
    @app.post('/places/')
    async def create_place_view(place: Place):
         return place
    
    @app.get('/')
    async def root():
         return {'message': 'Hello World!'}
    

In this code, you created a model that contains the fields you should expect for a place: a name and description, whether the place has coffee, wifi, and/or food, and a latitude and longitude so that you can locate it on a map.

Don't worry about the orm_mode bit yet. That's for use later when you hook up a database.

The create_place method simply takes a Place as a parameter and returns it. Soon, you'll actually save it to a database so it persists.

Try it out in the interactive API docs. Select the /places/ route, and click the try it out button. Fill in some values for the example place (or just use the defaults) and press execute.

Notice FastAPI also gives you a URL command string for your request, so you can copy and paste it into your terminal or use it in scripts!

What you have so far demonstrates how to send and receive data from the FastAPI application. The code is still simple, but there is a lot going on including validation and serialization – much of which FastAPI gives us for “free”. You should start to see what makes FastAPI fast (as in fast to develop).

Adding a Database

Sending and receiving data to our application is great, and for some applications, that's all you need to do. However, we’re building a database of remote working locations, so we’ll need to persist the Places to disk somehow. The best way is by using a database.

Setting up a database is going to require a little more configuration and the installation of some more software. First install SQLAlchemy, a “Python Toolkit and Object Relational Mapper.”:

pip install sqlalchemy --pre

For this demonstration you'll be using SQLite3 for your database since it requires no special setup or servers to run. Edit main.py so that it looks like this:

    from fastapi import FastAPI, Depends
    from pydantic import BaseModel
    from typing import Optional, List
    from sqlalchemy import create_engine
    from sqlalchemy.orm import declarative_base, sessionmaker, Session
    from sqlalchemy import Boolean, Column, Float, String, Integer
    
    app = FastAPI()
    
    #SqlAlchemy Setup
    SQLALCHEMY_DATABASE_URL = 'sqlite+pysqlite:///./db.sqlite3:'
    engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True, future=True)
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    Base = declarative_base()
    
    #Dependency
    def get_db():
         db = SessionLocal()
         try:
             yield db
         finally:
             db.close()
    
    class Place(BaseModel):
        name: str
        description: Optional[str] = None
        coffee: bool
        wifi: bool
        food: food
        lat: float
        lng: float
    
        class config:
            orm_mode = True
    
    @app.post('/places/')
    async def create_place_view(place: Place):
        return place
    
    @app.get('/')
    async def root():
        return {'message': 'Hello World!'}
    
That is a lot of code specific to SQLAlchemy. You can check out the documentation if you'd like to know what each individual method does later, but for now just know that the code you wrote:

  • Creates a SQLAlchemy database engine definition for SQLite.
  • Creates a blueprint for a SQLAlchemy session.
  • Defines a base model, which allows you to define Python objects as SQLAlchemy ORM objects.
  • Creates a method get_db which will be executed whenever you need access to the database. This method instantiates a Session from the blueprint you defined earlier, and closes it when you are done with it so that you don’t have unused sessions laying around.
Next, define a Place database SQLAlchemy model along with an instruction to create the table.

Add the following code just below the get_db() method:

class DBPlace(Base):
    __tablename__ = 'places'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50))
    description = Column(String, nullable=True)
    coffee = Column(Boolean)
    wifi = Column(Boolean)
    food = Column(Boolean)
    lat = Column(Float)
    lng = Column(Float)

Base.metadata.create_all(bind=engine)
You just defined the object that will be used to actually fetch and insert rows into the database.

Next you should define some methods to insert and fetch places from the database.

Add the following code Just after your class Place(BaseModel): class:

def get_place(db: Session, place_id: int):
    return db.query(DBPlace).where(DBPlace.id == place_id).first()

def get_places(db: Session):
    return db.query(DBPlace).all()

def create_place(db: Session, place: Place):
    db_place = DBPlace(**place.dict())
    db.add(db_place)
    db.commit()
    db.refresh(db_place)

    return db_place
These three methods are responsible for getting a single Place, getting all the Places, and creating a new Place.

The first parameter is always db: its type is a SQLAlchemy session. The rest of the parameters depend on what you're going to do:

  • For retrieving a single place, you just need the place_id.
  • For creating a place, you want the entire Pydantic Place model so you can create a record from it.
  • For retrieving all places you don't need any more information, you just return all the Places in the database.
Finally you should define some routes to perform the actions we need.

Replace the create_place_view method you created earlier with the following code:

@app.post('/places/', response_model=Place)
    def create_places_view(place: Place, db: Session = Depends(get_db)):
    db_place = create_place(db, place)
    return db_place

@app.get('/places/', response_model=List[Place])
def get_places_view(db: Session = Depends(get_db)):
    return get_places(db)

@app.get('/place/{place_id}')
def get_place_view(place_id: int, db: Session = Depends(get_db)):
    return get_place(db, place_id)
Note that both the create_places_view and get_places_view methods are called from the same endpoint: /places/. In this case, the HTTP method determines which function is called. HTTP methods are like verbs: they convey the intent of the client. When a GET request is sent get_places_view is called because we want to get the resource. Conversely when a POST request is sent create_places_view is called because we want to post (like posting a letter in the mail) the resource. It is important to use the correct HTTP methods for your actions.

All together, your main.py file should look like this:

    from fastapi import FastAPI, Depends
    from pydantic import BaseModel
    from typing import Optional, List
    from sqlalchemy import create_engine
    from sqlalchemy.orm import declarative_base, sessionmaker, Session
    from sqlalchemy import Boolean, Column, Float, String, Integer
    
    app = FastAPI()
    
    # SqlAlchemy Setup
    SQLALCHEMY_DATABASE_URL = 'sqlite+pysqlite:///./db.sqlite3:'
    engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True, future=True)
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    Base = declarative_base()
    
    def get_db():
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()
    
    # A SQLAlchemny ORM Place
    class DBPlace(Base):
        __tablename__ = 'places'
    
        id = Column(Integer, primary_key=True, index=True)
        name = Column(String(50))
        description = Column(String, nullable=True)
        coffee = Column(Boolean)
        wifi = Column(Boolean)
        food = Column(Boolean)
        lat = Column(Float)
        lng = Column(Float)
    
    Base.metadata.create_all(bind=engine)
    
    #A Pydantic Place
    class Place(BaseModel):
        name: str
        description: Optional[str] = None
        coffee: bool
        wifi: bool
        food: food
        lat: float
        lng: float
    
        class config:
            orm_mode = True
    
    # Methods for interacting with the database
    def get_place(db: Session, place_id: int):
        return db.query(DBPlace).where(DBPlace.id == place_id).first()
    
    def get_places(db: Session):
        return db.query(DBPlace).all()
    
    def create_place(db: Session, place: Place):
        db_place = DBPlace(**place.dict())
        db.add(db_place)
        db.commit()
        db.refresh(db_place)
    
        return db_place
    
    # Routes for interacting with the API
    @app.post('/places/', response_model=Place)
    def create_places_view(place: Place, db: Session = Depends(get_db)):
        db_place = create_place(db, place)
        return db_place
    
    @app.get('/places/', response_model=List[Place])
    def get_places_view(db: Session = Depends(get_db)):
        return get_places(db)
    
    @app.get('/place/{place_id}')
    def get_place_view(place_id: int, db: Session = Depends(get_db)):
        return get_place(db, place_id)
    
    @app.get('/')
    async def root():
        return {'message': 'Hello World!'}
    
This code can be broken down into five distinct sections (after imports):

  • Setup code for SQLAlchemy for the database connection and initialization.
  • A SQLAlchemy ORM Place definition, used when fetching and inserting records into the database.
  • A Pydantic Place definition, used with receiving data from the user as well as sending data to the user.
  • Methods specific to interacting with the database.
  • Routes for interacting with the API corresponding with actions to perform on a Place.
Open up the auto-generated docs in your browser, you should see these news endpoints listed. You can also interact with them. Try creating a few places using the “Post /places/” endpoint. Once that is done, use the “Get /places/” endpoint to retrieve them.

Conclusion 

In this mini Python FastAPI course you:

Learned the basics of working with Python FastAPI projects, including FastAPI best practices.
Created a CRUD app for remote working locations.
Integrate a database with your application.
Congratulations! You now have a fully functional Python FastAPI that serves a database of remote working locations. To take this project further to something that real-world CodingNomads could use, you'd probably want to create a client for your API: a website, a phone app, or even another API!

To go more in depth with FastAPI, including how to deploy your application so that others can use it, check out the excellent FastAPI Docs.

2 comments

  1. this post is very interesting and easy to read
  2. this post is helpful for me..