Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.git
.gitignore
.venv
__pycache__
.pytest_cache
*.pyc
*.pyo
*.pyd
*.swp
*.swo
tests
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL=postgresql://postgres:postgres@db:5432/fastapi_template
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,5 @@ dmypy.json
# Pyre type checker
.pyre/

.idea
.idea
test.db
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.10-slim

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
107 changes: 54 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,89 @@
<p align="center">
<img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FAST API"/>
<h2 align="center"> FastAPI Template </h2>
<h4 align="center"> A template for the beginners </h4>

<h4 align="center"> A template for beginners </h4>

---
## About
This is a beginner's template for getting started with FastAPI.
It uses SQLAlchemy as the ORM.

Contributions are welcome.
This is a beginner-friendly template for getting started with FastAPI and SQLAlchemy.

## Features
- [x] Database connection using SQLAlchemy
- [x] FastAPI server
- [x] Unit testing with PyTest
- [x] Basic CRUD for posts

- [x] Database Connection Using SQLAlchemy
- [x] FastAPI Server
- [x] Unit Testing with PyTest
- [x] Basic CRUD for Posts
## Requirements
- Python 3.10+
- `pip`
- PostgreSQL database

<br>
## Setup
1. Create and activate a virtual environment:

## Dependencies
```bash
python3 -m venv .venv
source .venv/bin/activate
```

2. Install dependencies:

```bash
pip install -r requirements.txt
```

- Python 3.7+
- Pip
- Other listed in requirements.txt
3. Set environment variables:

## Running
| Key | Value |
| --- | --- |
| `DATABASE_URL` | `postgresql://user:password@host:port/db` |

- Clone the repo using
Example (`.env`):

```bash
git clone https://github.com/mdhishaamakhtar/fastapi-sqlalchemy-postgres-template
```env
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/fastapi_template
```

- Create a Virtual Environment using
4. Run the API:

```bash
sudo pip install virtualenv
virtualenv env
uvicorn main:app --reload
```

- Activate the virtualenv
## API Docs (Swagger / OpenAPI)
Once the app is running locally, open:
- Swagger UI: `http://127.0.0.1:8000/docs`
- ReDoc: `http://127.0.0.1:8000/redoc`
- OpenAPI JSON: `http://127.0.0.1:8000/openapi.json`

## Running tests
```bash
env\Scripts\activate # for windows
source env/bin/activate # for linux and mac
pytest
```

- Install dependencies
## Formatting (Black)
Run formatter:

```bash
pip install -r requirements.txt
black .
```

- Setting up environment variables
## Docker (Local)
1. Create `.env` from example:

| Key | Value |
| ----------- | ----------- |
| DATABASE_URL | postgresql://user:password@host:port/db|
```bash
cp .env.example .env
```

- To run the project
2. Start API + Postgres with Docker Compose:

```bash
uvicorn main:app
docker compose up --build
```

## Contributors

<table>
<tr align="center">
<td>
Md Hishaam Akhtar
<p align="center">
<img src = "https://user-images.githubusercontent.com/58990970/103586688-9cde9700-4f0b-11eb-915c-0d8b9a555159.JPG" width="150" height="150" alt="Md Hishaam Akhtar">
</p>
<p align="center">
<a href = "https://github.com/mdhishaamakhtar">
<img src = "https://www.iconninja.com/files/241/825/211/round-collaboration-social-github-code-circle-network-icon.svg" width="36" height = "36" alt="GitHub"/>
</a>
<a href = "https://www.linkedin.com/in/mdhishaamakhtar">
<img src = "https://www.iconninja.com/files/863/607/751/network-linkedin-social-connection-circular-circle-media-icon.svg" width="36" height="36" alt="LinkedIn"/>
</a>
</p>
</td>
</tr>
</table>
3. API will be available at:
- `http://127.0.0.1:8000`
- Swagger: `http://127.0.0.1:8000/docs`
- ReDoc: `http://127.0.0.1:8000/redoc`

`DATABASE_URL` is passed to the API container from `.env` through `docker-compose.yml`.
7 changes: 4 additions & 3 deletions database/connection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import cast

from decouple import config
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import declarative_base, sessionmaker

SQLALCHEMY_DATABASE_URL = config("DATABASE_URL")
SQLALCHEMY_DATABASE_URL = cast(str, config("DATABASE_URL"))

engine = create_engine(SQLALCHEMY_DATABASE_URL)

Expand Down
33 changes: 33 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
services:
api:
build:
context: .
dockerfile: Dockerfile
container_name: fastapi_app
ports:
- "8000:8000"
environment:
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@db:5432/fastapi_template}
depends_on:
db:
condition: service_healthy

db:
image: postgres:16-alpine
container_name: fastapi_db
environment:
POSTGRES_DB: fastapi_template
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d fastapi_template"]
interval: 5s
timeout: 5s
retries: 10

volumes:
postgres_data:
89 changes: 33 additions & 56 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,56 +1,33 @@
aiofiles==0.5.0
aniso8601==7.0.0
appdirs==1.4.4
async-exit-stack==1.0.1
async-generator==1.10
atomicwrites==1.4.0
attrs==20.3.0
black==20.8b1
certifi==2020.12.5
chardet==4.0.0
click==7.1.2
colorama==0.4.4
dnspython==2.1.0
email-validator==1.1.2
fastapi==0.63.0
graphene==2.1.8
graphql-core==2.3.2
graphql-relay==2.0.1
greenlet==1.0.0
h11==0.12.0
idna==2.10
iniconfig==1.1.1
isort==5.8.0
itsdangerous==1.1.0
Jinja2==2.11.3
MarkupSafe==1.1.1
mypy-extensions==0.4.3
orjson==3.5.2
packaging==20.9
pathspec==0.8.1
pluggy==0.13.1
promise==2.3
psycopg2==2.8.6
py==1.10.0
pydantic==1.8.2
pyparsing==2.4.7
pytest==6.2.3
pytest-dependency==0.5.1
python-decouple==3.4
python-dotenv==0.17.0
python-multipart==0.0.5
PyYAML==5.4.1
regex==2021.4.4
requests==2.25.1
Rx==1.6.1
six==1.15.0
SQLAlchemy==1.4.11
starlette==0.13.6
toml==0.10.2
typed-ast==1.4.3
typing-extensions==3.7.4.3
ujson==3.2.0
urllib3==1.26.5
uvicorn==0.13.4
watchgod==0.7
websockets==9.1
Pygments==2.19.2
SQLAlchemy==2.0.48
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
black==26.1.0
certifi==2026.2.25
click==8.3.1
exceptiongroup==1.3.1
fastapi==0.135.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.11
iniconfig==2.3.0
mypy_extensions==1.1.0
packaging==26.0
pathspec==1.0.4
platformdirs==4.9.4
pluggy==1.6.0
psycopg2-binary==2.9.11
pydantic==2.12.5
pydantic_core==2.41.5
pytest-dependency==0.6.1
pytest==9.0.2
python-decouple==3.8
python-multipart==0.0.22
pytokens==0.4.1
starlette==0.52.1
tomli==2.4.0
typing-inspection==0.4.2
typing_extensions==4.15.0
uvicorn==0.41.0
5 changes: 3 additions & 2 deletions routes/posts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
Expand Down Expand Up @@ -27,14 +28,14 @@ def get_all_posts(db: Session = Depends(get_db)):


@router.get("/get/{id}", status_code=status.HTTP_200_OK, response_model=Post)
def get_one_post(id, db: Session = Depends(get_db)):
def get_one_post(id: UUID, db: Session = Depends(get_db)):
return post_get_one(db=db, id=id)


@router.delete(
"/delete/{id}", status_code=status.HTTP_200_OK, response_model=DeletePostResponse
)
def delete_post(id, db: Session = Depends(get_db)):
def delete_post(id: UUID, db: Session = Depends(get_db)):
delete_status = post_delete(db=db, id=id)
if delete_status.detail == "Doesnt Exist":
raise HTTPException(
Expand Down
10 changes: 4 additions & 6 deletions schemas/models.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
from typing import Optional
from uuid import UUID

from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict


class HealthResponse(BaseModel):
status: str


class Post(BaseModel):
id: Optional[UUID]
id: Optional[UUID] = None
title: str
description: str

class Config:
orm_mode = True
model_config = ConfigDict(from_attributes=True)


class DeletePostResponse(BaseModel):
Expand All @@ -26,5 +25,4 @@ class UpdatePost(BaseModel):
title: str
description: str

class Config:
orm_mode = True
model_config = ConfigDict(from_attributes=True)
Loading
Loading