Flask JSON API services
Make Post From Draft
SendEmailThatPostIsPublished(
MakePostFromDraftService(draft_repo=PostDraftRepository(), post_repo=PostRepository())
).publish(post_draft_id="put_real_post_draft_id_here")
# decorator
# pattern
Kacper Walczak · 29-12-2023
Flask API with services
Learn how, why and when to use services
with Flask and easy to test architecture.
Introduction
This is
secondpart of the series about Flask JSON API. You don't need to visit first article catch up with this one.This article is a part of Flask JSON API series.
We start with the code at the point where first article ended.
Follow first article or clone repository from GitHub (opens in a new tab) to catch up.
Services
Services are a way to separate business logic from the rest of the code. It's a good practice to keep your code clean and easy to test.
What is a service?
Service is a class that contains business logic, can be used in multiple places and is easy to test.
This example omits some details like try/catch, batch, transactions, etc. to keep it simple.
class MakePostFromDraftService(MakePostFromDraft): # interface described below
def __init__(self, draft_repo: PostDraftRepository, post_repo: PostRepository):
self.draft_repo = draft_repo
self.post_repo = post_repo
def publish_draft(self, post_draft_id: str) -> BlogPost:
post_draft = self.draft_repo.get_by_id(post_draft_id)
post = BlogPost(
filename=post_draft.title,
content=post_draft.content,
author=post_draft.author,
created_at=post_draft.created_at,
updated_at=post_draft.updated_at,
)
self.post_repo.save(post)
self.draft_repo.delete(post_draft_id)
return postUsage of this service is very simple. Just pass repositories to the constructor and call publish_draft method.
service = MakePostFromDraftService(draft_repo=PostDraftRepository(), post_repo=PostRepository())
blog_post = service.publish_draft(post_draft_id="put_real_post_draft_id_here")Why services?
Services are a good place to put your business logic with proper abstraction -> so can be reused and decorated.
Services to work properly needs interfaces. This is a good way to keep your code clean and easy to test.
Without service interfaces it's very hard to test easily e.g. controllers.
Interfaces
Interfaces are a way to define what methods should be implemented in a class. It's a good practice to use interfaces to keep your code easy to reuse/reimplement/decorate.
class MakePostFromDraft(ABC):
@abstractmethod
def publish_draft(self, post_draft_id: str) -> BlogPost:
passWhat it gives us? We can use this interface to create a service that will send an email after publishing a post with decorator pattern.
Decorator pattern
Decorators are a way to add additional functionality to a function. But not python decorators, but decorator pattern.
It's a good practice to use decorators to keep your code clean and
easy to test(because every part can be tested separately).
class SendEmailThatPostIsPublished(MakePostFromDraft):
"""Decorator pattern"""
def __init__(self, make_post_from_draft_service: MakePostFromDraft):
self.make_post_from_draft_service = make_post_from_draft_service
def publish_draft(self, post_draft_id: str) -> BlogPost:
post = self.make_post_from_draft_service.publish_draft(post_draft_id)
self.send_email(post)
return post
def send_email(self, post: BlogPost):
# send email about created post to post.author_idExample usage
def make_post_from_draft_endpoint(request: Request):
post_draft_id = request.json.get("post_draft_id")
post = SendEmailThatPostIsPublished(
MakePostFromDraftService(draft_repo=PostDraftRepository(), post_repo=PostRepository())
).publish(post_draft_id)
return jsonify(post.to_dict())Testing services
Unit testing
Services are easy to test because they are easy to mock. You can mock repositories and test only business logic.
def test_publish_draft():
# given
draft_repo = Mock()
post_repo = Mock()
service = MakePostFromDraftService(draft_repo=draft_repo, post_repo=post_repo)
post_draft_id = "test_post_draft_id_here"
post_draft = PostDraft(
id=post_draft_id,
title="title",
content="content",
author="author",
created_at=datetime.now(),
updated_at=datetime.now(),
)
draft_repo.get_by_id.return_value = post_draft
# when
service.publish_draft(post_draft_id)
# then
draft_repo.get_by_id.assert_called_once_with(post_draft_id)
post_repo.save.assert_called_once_with(
BlogPost(
filename=post_draft.title,
content=post_draft.content,
author=post_draft.author,
created_at=post_draft.created_at,
updated_at=post_draft.updated_at,
)
)
draft_repo.delete.assert_called_once_with(post_draft_id)Integration testing
You can test endpoints that uses services easily.
def test_make_post_from_non_existing_draft(client): # client from first article
# given
post_draft_id = "fake_post_draft_id_here"
# when
make_post_from_non_existing_draft_response = client.post(
"/make_post_from_draft",
json={"post_draft_id": post_draft_id},
)
# then
assert make_post_from_non_existing_draft_response.status_code == 404That's it! Now you can use this as a base for your extended needs with JSON API.
Next
You can go to the following articles about Flask JSON API series.
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.
06-03-2026
Build your own local voice assistant powered by Ollama.
06-03-2026
Generate YouTube thumbnails with FastAPI and Ollama.
05-09-2024
Compare Neo4j and Tigergraph databases, which is easier to work with, etc.