project
Featured

Building a Media-Tracking REST API in Rust: A Deep Dive into 'Completionist'

As someone who loves games, books, and movies, I've always wanted a single place to track what I'm watching, reading, and playing. While many apps do this for one type of media, I wanted one app to rule them all. This led me to create Completionist API, a backend service designed to be the central hub for all my media-tracking needs.

Cristopher Cousiño
Author
Saturday, February 8, 2025
Published
5 min read
Read time

As someone who loves games, books, and movies, I've always wanted a single place to track what I'm watching, reading, and playing. While many apps do this for one type of media, I wanted one app to rule them all. This led me to create Completionist API, a backend service designed to be the central hub for all my media-tracking needs. I chose to build it in Rust, and this post explores the journey, the architecture, and the key features of the project.

Why Rust and Axum?

For a backend service that needs to be reliable, performant, and safe, Rust was a natural choice. Its strong type system and ownership model eliminate entire classes of bugs at compile time. I paired it with Axum, a modern and ergonomic web framework built by the tokio team. Axum's modularity, powerful extractor system, and seamless integration with the async ecosystem made it a joy to work with.

The Core Architecture: Layers of Responsibility

I designed the API with a clear, layered architecture to keep the code organized and maintainable:

  1. Routes (routes/): Defines the API endpoints, handles incoming HTTP requests, and uses Axum extractors to parse data like JSON payloads and path parameters.
  2. Database (db/): Contains all the database logic. By using sqlx, I could write raw SQL queries that are checked against the actual database schema at compile time.
  3. Services (services/): Manages business logic and integration with external APIs. For example, it fetches movie data from OMDb or manga details from Jikan.
  4. Models (models/): Defines the data structures used throughout the application, such as User, MediaItem, and UserListItem.
  5. Authentication (auth/): A dedicated module for handling security.

Feature Highlights

Secure and Modern Authentication

No API is complete without secure authentication. The process is twofold:

  • Registration: When a user registers, their password isn't stored directly. Instead, I use the argon2 crate to produce a strong, salted hash of the password.
  • Login & Authorization: On login, the provided password is hashed and compared to the stored hash. If they match, a JSON Web Token (JWT) is generated.

A Smart Database that Evolves

The application is backed by a PostgreSQL database, run inside a Docker container for easy setup. The database schema started simple but grew as I added features. This evolution is managed by sqlx-cli migrations.

One of the most interesting database functions is find_or_create_media_item. When a user adds a movie from OMDb, for example, the system first checks if a media_items record with source = 'OMDB' and the corresponding external_id already exists. If it does, it uses the existing record. If not, it creates a new one.

Self-Documenting with OpenAPI

Writing API documentation by hand is tedious and error-prone. To solve this, I used the incredible utoipa crate. By adding a few macros to my route handlers and data models, it automatically generates a complete OpenAPI v3 (Swagger) specification.

The application serves a beautiful Swagger UI, allowing anyone (including my future self or a potential frontend developer) to explore the API's endpoints, see the required data structures, and even test the endpoints directly from the browser.

Final Thoughts

Building the Completionist API has been a fantastic learning experience. It solidified my understanding of Rust for backend development and gave me hands-on experience with the entire lifecycle of a modern web service—from database design and secure authentication to external API integration and documentation.

RELATED

Continue Reading

Enjoyed this article?

Check out more of my thoughts and projects, or get in touch to discuss your next idea.