This is a simple microblogging platform I developed to get some hands-on experience with a microservice architecture. The project consists of three main components, each of which is implemented as a separate FastAPI and SQLModel app:
Handles creating users, verifying passwords, and issuing JWTs for authentication
Handles creating posts by authenticated users
Displays the feed of posts in reverse chronological order, with optional filtering. This service uses a denormalized database design, storing user information directly with posts to optimize read performance.
There are also some peripheral services:
An NGINX server that routes requests to one of the above services
A Next.js app to display the current feed, log in, and create a post. This is built as a static site (not using Next's server-side rendering) so that it can served by the API Gateway.
Designed to handle development or admin tasks outside of the normal user flow. Currently, this will populate the User and Post Service databases with random data if there are empty and then ensure that the Post and Feed databases are in sync.
Used by the Post Service to notify the Feed Service of newly-created posts
To start the services for development, use docker-compose up --build. If you're working on the front-end, pnpm build_watch will automatically rebuild the application on any change. This requires nodemon, a generally useful tool for monitoring file changes and restarting processes.
The "production-ready" version is docker-compose -f ./compose.yaml -f ./compose.prod.yaml up (but also, see the Known Limitations section). The main difference is that compose.prod.yaml will build the front-end in a separate container and then copy it into the Nginx API Gateway container instead of just mounting the local frontend/build/ directory.
The Internal Service will automatically create users with random usernames, but they all have password password. To find a valid username, you can GET /users/users, look at the feed in the front-end and lift a name from there, or inspect the database directly with something like docker-compose exec user-service-db psql.
You can run the integration tests with docker-compose -f ./compose.test.yaml up --build --abort-on-container-exit --exit-code-from integration_tests. There's no simple commands for the service-specific tests; for those, you'll have to cd into the directory and run pytest (keeping in mind that the individual services have slightly different package requirements, so you'll need a service-specific virtual environment).
This was a project I wrote as a learning experience; while I'm satisfied with it, there are still a number of features that are incomplete if I wanted to actually deploy this.
- None of the "secrets" are actually secret. They're all under version control to demonstrate the structure of the project, and they're all descriptive strings rather than randomly-generated by a cryptographically-secure process.
- Error handling is intentionally basic. I wrote enough so that I felt confident that I understood the correct way to handle FastAPI and SQLModel errors, but for simplicity I generally assumed that incoming data was valid, that a given user ID would actually exist, and so forth. I think this is completely reasonable for a learning project of this size, but I want to be clear that I understand that this level of error handling wouldn't be acceptable in a production environment.
- Similarly, the unit tests aren't quite up to my standards—there are a lot of individual functions and unhappy-paths that aren't covered.
- There are some endpoints that are implemented in the API but not actually exposed in the front-end; most notably, there's no way to actually create a user from the front-end. (You can, however, use curl or Postman or similar to
POST /users/users/a JSON blob withusername,display_name, andpasswordfields.) - Similarly, there's scaffolding for updating user information or deleting individual posts, but it's not fleshed out.
- I started looking into PostgreSQL's
tsvectorto add search functionality to the Feed Service, but it didn't feel necessary for a learning project. Still, it's certainly something I'd want to include in a live application. - This uses simple, short-lived JWTs for authentication; it would be nice to implement a more robust flow involve refresh tokens.
- I didn't set up Alembic for this project because I was comfortable just deleting the database each time the schema changed and starting from scratch, but that isn't sustainable.
I'm used to working with monorepos, so this was a great opportunity for me to explore a different architecture. I had to think carefully about the proper ways for separate services to communicate, what data belongs where, and how to coordinate responsibilities across independently running components. I was able to see how separating services like this could allow multiple teams to work in parallel, possibly using different languages, frameworks, or scaling strategies. However, in the context of a single coder running everything locally, it was overkill. I'll probably stick to monorepos for personal projects in the future, but I do feel better prepared to work with more distributed architecture when the need arises.
Similarly, while Next.js is easy to use and clearly powerful, many of its features are geared toward server-side rendering or hybrid applications, whereas I knew from the start that I wanted a traditional single-page app served statically by NGINX. In retrospect, plain React might have been a better fit. That said, it did make me eager to try Next in future projects where the advanced features could shine.
FastAPI also impressed me with its clean syntax, smooth integration with Pydantic models, and excellent auto-generated Swagger documentation. Coming from Django, I did miss features that are built in there, like global settings, centralized database configuration, or automatic test DB handling, but overall FastAPI was a joy to work with. (Really, though—I know it's trying to be a minimalist framework, but given how important databases are to REST APIs, it's surprising that FastAPI treats them as a third-party concern.)
Overall, this project pushed me out of my architectural and technological comfort zone. I wouldn't necessarily choose this architecture again for projects where I'm the sole developer, but I'm glad I built it. It helped me see when and why certain patterns shine, and gave me a taste of the complexity involved in scaling real-world systems.
