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.

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
mkdir fastnomads
cd fastnomads
python3 -m venv env
source env/bin/activate
pip install fastapi uvicorn
Starting with “Hello World”
from fastapi import FastAPI, Depends app = FastAPI() @app.get('/') async def root(): return {'message': 'Hello World!'}
@app.get('/')
async def root():
return {'message': 'Hello World!'}
uvicorn main:app --reload
{ "message": "Hello World!" }
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!'}
pip install sqlalchemy --pre
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!'}
- 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.
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)
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
- 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.
@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)
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!'}
- 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.