- Introduction
- Features
- Project Structure, Implementation details and Best Practices
- Requirements
- Setup
- Documentation and Usage
- Stack
- Roadmap
- License
SimpleRESTBlog is a fast, fully async and reliable blog system RESTful API built with Python and FastAPI framework. It uses the open source PostgreSQL database for storage.
Note: This project is under development and is not fully ready! see the Roadmap section.
- Fully async and non-blocking.
- Uses FastAPI framework for API development
- Uses PostgreSQL as data store for users, drafts, posts and comments.
- Fully layered and decoupled code.
- Extensible architecture for adding new API endpoints and services.
- Descriptive and well-documented code.
- OAuth2 (with hashed passwords and JWT tokens) based user authentication.
- TOTP for two-factor authentication using authenticator apps like Google Authenticator.
- Uses Poetry for dependency management.
- Automated code formatting and linting with pre-commit and ruff.
- Pagination support for listing comments.
- Fully type annotated code for better IDE support and code quality.
Structure of simplerestblog
package containing main files and folders of the application is consistent and straightforward
and just by looking at module names it gives you an idea of what's inside it!
simplerestblog
├── alembic # Migration utility
├── src # Primary app
│ ├── core # config, ACL, DB, depends, schemas, security, ...
│ ├── repository # Data layer: Repository and ORM stuff
│ ├── service # Service layer: the business logic
│ ├── web # API layer: routes
│ ├── app.py # Main FastAPI app
│ ├── __init__.py
│ └── __main__.py # Runs the uvicorn server
├── tests # App tests
├── alembic.ini # Migration settings
└── pyproject.toml
At the core of almost any business, a good database design helps the reliability and data normalization and avoid wasting disk usage.
The database design and considerations of SRB is discussed below.
Tables:
users
+------------------+----------+------+---------+-----------+------+-----+----+
| username: UNIQUE | password | role | created | totp_hash | name | ... | id | <------------+
+------------------+----------+------+---------+-----------+------+-----+----+ |
^^ |
||-----------+ |
| | |
drafts | | |
+----+-------+------+---------+------------+--------------+---------+--------------+ | |
| id | title | body | updated | draft_hash | is_published | created | username: FK | | |
+----+-------+------+---------+------------+--------------+---------+--------------+ | |
^ | |
|----------------------------+ +----------------------------------------+ |
| | |
posts | | |
+------+---------------+--------------+--------------+----+ |
| slug | published: DT | draft_id: FK | username: FK | id | <-+ |
+------+---------------+--------------+--------------+----+ | |
^ | |
+-----------------------------| | |
| | |
association_table | | |
+----+------------+-------------+ | |
| id | tag_id: FK | post_id: FK | | |
+----+------------+-------------+ | |
| | |
| | |
tags | | |
+----+-----+ | | |
| id | tag | <--+ | |
+----+-----+ | |
| |
comments | |
+----+---------+-------------+---------------+---------+-------------+---------------+--------------+
| id | comment | path: LTree | commented: DT | updated | post_id: FK | parent_id: FK | username: FK |
+----+---------+-------------+---------------+---------+-------------+---------------+--------------+
^ |
|-------------------------------------------------------------------------+
As you can see, data is normalized. Some design considerations of the database design:
- Each post's details are stored in the
drafts
table. This is good because if someone wants to unpublish and edit the post, theis_published
columns of thedrafts
table will befalse
and there is no need to delete post details and reinsert them into database (if they were stored inposts
table separately) - Retrieving the comments is done by using the PostgreSQL feature LTree (which is a very fast, builtin type for hierarchical tree-like data)
The data layer of the application has repositories for different database tables.
- In each method of the repositories queries are generated using SQLAlchemy query builder functions and methods, to have full control over the generated query and more important database hits.
-
SRB uses access and refresh
JWT
tokens to implement authentication.Signing up is a simple
username
,password
flow. -
2FA:
SRB has builtin support for TOTP two-factor authentication using qr-code and authenticator apps like: Google Authenticator
Most routes of SRB are protected (i.e. you have to be logged in and provide your access token) However SRB implements different layer of permissions for routes which in short is the ACL. SRB handle the ACL and route access permission with JWT tokens.
For example if I am the superuser or admin of SRB I can delete any user comments with the route that user uses to remove his/her comment. Also, if a users attempts to delete some other user's comment, it is now allowed.
SRB is designed as a layered system. There are three layers: Data layer, Service layer and API layer which are fully decoupled. and upper layers are not aware of the details in the lower layers which is achieved with dependency injection.
Manual installation:
- Python 3.10 or higher.
- Poetry for dependency management.
- Up and running PostgreSQL instance.
- Up and running Redis instance.
git clone https://github.com/mahdihaghverdi/simplerestblog.git
You need to configure Poetry to place the virtual environment in the project directory. To do so, run the following command:
poetry config --local virtualenvs.in-project true
Then, install dependencies:
poetry install
You can see the table of all environment variables below. Those marked with *
are required and MUST be set before
running the application. Rest of them are optional and have default values.
Note: To set any of these environment variables below, you MUST prefix them with SRB_
and then set them
in your shell or in a .env
file placed at the root directory.
For example, to set DEBUG
environment variable, you can do the following:
export SRB_DEBUG=True
Also note that ALL environment variables are CASE SENSITIVE.
Name | Description | Default | Type |
---|---|---|---|
API_VERSION |
API version prefix | v1 |
string |
DEBUG |
Debug mode for development | False |
boolean |
Name | Description | Default | Type |
---|---|---|---|
PG_URL |
Postgres connection URL | postgresql+asyncpg://postgres:[email protected]:5432 |
string |
Name | Description | Default | Type |
---|---|---|---|
REDIS_CACHE_URL |
Redis instance connection URL | redis://@0.0.0.0:6379 |
string |
Name | Description | Default | Type |
---|---|---|---|
ACCESS_TOKEN_EXPIRE_MINUTES |
Access token expiration time in minutes | 120 (2 hours) |
integer |
REFRESH_TOKEN_EXPIRE_MINUTES |
Refresh token expiration time in minutes | 2880 (2 days) |
integer |
SECRET_KEY * |
Secret key for signing JWT tokens | 64 random characters generated by openssl rand -hex 64 command. |
string |
TFA_EXPIRE_MINUTES |
TOTP expiration time | 15 |
string |
An up & running PostgreSQL and Redis instance are required.
python -m src
If the SRB_DEBUG
environment variable is set to True
, the application will be run in debug mode and will be
reloaded automatically on code changes.
Now your application is available at http://localhost:8000/
.
After running the application, you can access the OpenAPI (Swagger) documentation at /api/v1/docs
endpoint.
Frameworks and technologies used in Shortify
- FastAPI (Web framework)
- PostgreSQL (Database)
- Redis (In-memory DB for 2FA)
- SQLAlchemy (ORM)
- poetry (Dependency Management)
- pre-commit (Git hook)
- ruff (Linter & Formatter)
- Implement a route to
unpublish
a post - Implement a route to
delete
a post - Use Redis for page caching
- Introduce likes in SRB
- Implement following and follower machnism
- Introduce notifications in SRB
- notif for adding comments and replies
- notif for likes
- follow or unfollow
This project is licensed under the terms of the GPL-3.0 license.
— ⚡ —