Flask CRUD JSON API

Flask JSON CRUD API

Get Post
Request

Response 200

{
"id": "blog_post_(&*&IAJSIDJAS",
"title": "Resize image with Python",
"content": "Learn how to resize image with Python",
"author_id": "user_OAIJSDOIJ"
}

Kacper Walczak · 26-12-2023

Flask JSON CRUD API

Get to know how to easily build json API

with Flask and easy to test architecture.

Introduction

In this article I will show you how to build a simple JSON API with Flask. We will use in memory to simulate DB. We will also use pytest to test our API.

Main goal is to show you architecture that let to build a json API with Flask and test it easily.

This article is a part of Flask JSON API series.

Setup

First of all we need to create a virtual environment. We will use venv module to do that.

python3 -m venv venv

Now we need to activate our virtual environment.

source venv/bin/activate

Now we can install Flask.

pip install Flask

We will also need pytest.

pip install pytest

Build JSON API

Project structure

We will create a simple project structure.

mkdir flask-json-api
cd flask-json-api
mkdir app
touch app/__init__.py
touch app/models.py
touch app/repository.py
touch app/routes.py
touch app/tests/__init__.py
touch app/tests/integration.py
touch main.py
touch requirements.txt

Models

We will create a simple model for our blog post.

app/models.py
from dataclasses import dataclass
from datetime import datetime
 
 
@dataclass
class BlogPost:
    id: str
    title: str
    content: str
    author_id: str
    created_at: datetime | None = None
    updated_at: datetime | None = None

Repository

Now we can create our repository.

app/repository.py
import uuid
 
from dataclasses import dataclass
from datetime import datetime
from typing import List
 
from app.models import BlogPost
 
 
@dataclass
class CreatePostDto:
    title: str
    content: str
    author_id: str
 
 
@dataclass
class UpdatePostDto:
    post_id: str
    title: str | None = None
    content: str | None = None
    author_id: str | None = None
 
 
class BlogPostRepository:
    def __init__(self):
        self.posts = []
 
    def get_all(self) -> List[BlogPost]:
        return self.posts
 
    def get_by_id(self, post_id: str) -> BlogPost | None:
        for post in self.posts:
            if post.id == post_id:
                return post
        return None
 
    def create(self, dto: CreatePostDto) -> BlogPost:
        post = BlogPost(id=str(uuid.uuid4()),
                        title=dto.title,
                        content=dto.content,
                        author_id=dto.author_id)
        self.posts.append(post)
        return post
 
    def update(self, dto: UpdatePostDto) -> BlogPost | None:
        post = self.get_by_id(dto.post_id)
        if post is None:
            return None
        post.title = dto.title if dto.title else post.title
        post.content = dto.content if dto.content else post.content
        post.author_id = dto.author_id if dto.author_id else post.author_id
        post.updated_at = datetime.now()
        return post
 
    def delete(self, post_id: str) -> bool:
        post = self.get_by_id(post_id)
        if post is None:
            return False
        self.posts.remove(post)
        return True
 
 
post_repo = BlogPostRepository()

Routes

Now we can create our routes.

app/routes.py
from flask import Blueprint, jsonify, request
 
from app.repository import post_repo, CreatePostDto, UpdatePostDto
 
bp = Blueprint("blog_posts", __name__, url_prefix="/blog_posts")
 
 
@bp.route("/", methods=["GET"])
def get_blog_posts():
    return jsonify({"blog_posts": post_repo.get_all()})
 
 
@bp.route("/", methods=["POST"])
def create_blog_post():
    data = request.get_json()
    blog_post = post_repo.create(dto=CreatePostDto(title=data.get("title"),
                                                   content=data.get("content"),
                                                   author_id=data.get("author_id")))
    return jsonify(blog_post.__dict__)
 
 
@bp.route("/<blog_post_id>", methods=["GET"])
def get_blog_post(blog_post_id):
    return jsonify({"blog_post": post_repo.get_by_id(blog_post_id)})
 
 
@bp.route("/<blog_post_id>", methods=["PUT"])
def update_blog_post(blog_post_id):
    data = request.get_json()
    blog_post = post_repo.update(dto=UpdatePostDto(post_id=blog_post_id,
                                                   title=data.get("title"),
                                                   content=data.get("content")))
    return jsonify(blog_post.__dict__ if blog_post else {})
 
 
@bp.route("/<blog_post_id>", methods=["DELETE"])
def delete_blog_post(blog_post_id):
    return jsonify({"deleted": post_repo.delete(blog_post_id)})

Main

Now we can create our main file.

See that we use Blueprint to register our routes. It's a good practice to separate routes from main file.

Move repository, routes and models to app/blog_posts/ directory. With this approach you created so-called modular application.

main.py
from flask import Flask
 
from app.routes import bp as blog_posts_bp
 
 
def create_app():
    app = Flask(__name__)
    app.register_blueprint(blog_posts_bp)
    return app
 
 
if __name__ == "__main__":
    app = create_app()
    app.run(debug=True)

Tests

Now we can create our tests. We are going to implement only integration tests for now.

In a scenario like this there is no point to write unit tests for it.

But when we will introduce some more logic to our app, we will need to write unit tests.

We will cover unit tests in next article, when example will need unit tests.

We will cover testing API as a whole in Flask JSON API unit testing article.

  • We will use in memory repository to simulate DB.
  • We will use pytest to test our API.
  • We will use client fixture to test our API endpoints.
app/tests/integration.py
import json
import pytest
 
from main import create_app
 
 
@pytest.fixture
def client():
    app = create_app()
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client
 
 
# Tests
test_posts_data = {
    "resize_image": {
        "title": "Resize image with Python",
        "content": "Learn how to resize image with Python",
        "author_id": "user_OAIJSDOIJ"
    }
}
 
 
def test_get_blog_posts(client):
    get_all_posts_response = client.get("/blog_posts/")
    assert get_all_posts_response.status_code == 200
    assert json.loads(get_all_posts_response.data) == {"blog_posts": []}  # empty list for now
 
 
def test_create_blog_post(client):
    create_post_response = given_a_blog_post_exists(client, blog_post_data=test_posts_data["resize_image"])
    assert create_post_response["title"] == test_posts_data["resize_image"]["title"]
    assert create_post_response["content"] == test_posts_data["resize_image"]["content"]
    assert create_post_response["author_id"] == test_posts_data["resize_image"]["author_id"]
 
 
def test_get_blog_by_id_post(client):
    given_post_in_db = given_a_blog_post_exists(client)
    get_blog_by_id_response = client.get(f"/blog_posts/{given_post_in_db['id']}")
    assert get_blog_by_id_response.status_code == 200
    assert json.loads(get_blog_by_id_response.data) == {"blog_post": given_post_in_db}
 
 
def test_update_blog_post(client):
    given_post_in_db = given_a_blog_post_exists(client)
    fields_to_update = {"title": "Updated Title"}
    update_response = client.put(
        f"/blog_posts/{given_post_in_db['id']}",
        data=json.dumps(fields_to_update),
        content_type="application/json",
    )
    assert update_response.status_code == 200
    assert json.loads(update_response.data)["title"] == "Updated Title"
 
 
def test_delete_blog_post(client):
    given_post_in_db = given_a_blog_post_exists(client)
    response = client.delete(f"/blog_posts/{given_post_in_db['id']}")
    assert response.status_code == 200
    assert json.loads(response.data) == {"deleted": True}
 
 
# Helper functions
def given_a_blog_post_exists(client, blog_post_data=None):
    if blog_post_data is None:
        blog_post_data = test_posts_data["resize_image"]
    create_post_response = client.post(
        "/blog_posts/",
        data=json.dumps(blog_post_data),
        content_type="application/json",
    )
    assert create_post_response.status_code == 200
    return json.loads(create_post_response.data)

Tests are passing, so we can move on.

Tests passing

Requirements file

Now we can create our requirements file.

requirements.txt
pytest~=7.4.3
Flask~=3.0.0

Run

Now we can run our app.

python main.py

Test

Now we can run our tests.

pytest app/tests/integration.py
 
# or with verbose flag (shows more details, for verbosity levels use -v or -vv, where -vv is the most verbose)
pytest app/tests/integration.py -vv

That's it! Now you can use this as a base for your simple JSON API with Flask.

Next

You can go to the following articles about Flask JSON API.

This article is a part of Flask JSON API series.

READ

Latest readings

  • Readings are sites which will help you with detailed

  • information about given topic. Read latest ones from Learn.

AI

06-03-2026

Local Voice Assistant with Ollama
  • Build your own local voice assistant powered by Ollama.

AI

06-03-2026

AI YouTube Thumbnail Generator
  • Generate YouTube thumbnails with FastAPI and Ollama.

Architecture

05-09-2024

Graph DB usage comparison
  • Compare Neo4j and Tigergraph databases, which is easier to work with, etc.