Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rust Blog Platform

A full-stack blog application written in Rust.

The project demonstrates a complete Rust backend with HTTP and gRPC APIs, PostgreSQL persistence, JWT authentication, a reusable client library, a CLI application, and an experimental WebAssembly frontend built with Leptos.

The backend provides user registration and authentication, as well as CRUD operations for blog posts. Public posts can be read without authentication, while creating, editing, and deleting posts requires a valid JWT token.

The WebAssembly frontend currently implements user registration and post creation.

Features

Backend

  • HTTP REST API with Actix Web
  • gRPC API with Tonic
  • PostgreSQL persistence with SQLx
  • Database migrations
  • JWT authentication
  • Password hashing with Argon2
  • Post CRUD operations
  • Authorization checks for post modification
  • Shared business logic between HTTP and gRPC APIs
  • Structured logging with tracing
  • Custom error handling with thiserror
  • CORS support for the browser frontend

Client Library

  • HTTP client based on reqwest
  • gRPC client based on Tonic
  • JWT token management
  • Authentication operations
  • Post CRUD operations
  • Pagination support

CLI

  • User registration and login
  • Create, get, update, delete and list posts
  • HTTP and gRPC transports
  • Local JWT token storage
  • Command-line interface implemented with clap

WebAssembly Frontend

The frontend is written in Rust using Leptos and compiled to WebAssembly.

Currently implemented:

  • user registration
  • JWT-based authenticated requests
  • post creation

The backend URL used by the frontend is currently configured in index.html.

Project Structure

blog-project/
├── Cargo.toml
├── README.md
│
├── blog-server/
│   ├── Cargo.toml
│   ├── build.rs
│   ├── migrations/
│   ├── proto/
│   │   └── blog.proto
│   └── src/
│       ├── main.rs
│       ├── domain/
│       ├── application/
│       ├── data/
│       ├── infrastructure/
│       └── presentation/
│
├── blog-client/
│   ├── Cargo.toml
│   ├── build.rs
│   └── src/
│
├── blog-cli/
│   ├── Cargo.toml
│   └── src/
│
└── blog-wasm/
    ├── Cargo.toml
    ├── index.html
    └── src/

Architecture

The backend follows a layered architecture:

Presentation
     │
     ▼
Application
     │
     ▼
Domain
     ▲
     │
Data / Infrastructure

Domain

Contains the core application models and domain errors:

  • User
  • Post
  • authentication request models
  • post creation/update models
  • domain-specific errors

Application

Contains the application business logic:

  • authentication service
  • blog service
  • authorization checks

The same application services are used by both the HTTP and gRPC presentation layers.

Data

Contains PostgreSQL repository implementations for users and posts.

Infrastructure

Contains infrastructure-specific functionality:

  • PostgreSQL connection pool
  • database migrations
  • JWT generation and validation
  • logging configuration

Presentation

Contains:

  • Actix Web HTTP handlers
  • JWT middleware
  • Tonic gRPC service implementation

Technology Stack

Component Technology
Language Rust
HTTP server Actix Web
gRPC Tonic / Prost
Database PostgreSQL
Database access SQLx
Authentication JWT
Password hashing Argon2
Serialization Serde
Error handling thiserror / anyhow
Logging tracing
HTTP client reqwest
CLI clap
WASM wasm-pack
Frontend Leptos
Protocol Protocol Buffers

API

Authentication

Public HTTP endpoints:

POST /api/auth/register
POST /api/auth/login

Successful authentication returns a JWT token.

Posts

Public endpoints:

GET /api/posts
GET /api/posts/{id}

Authenticated endpoints:

POST   /api/posts
PUT    /api/posts/{id}
DELETE /api/posts/{id}

Authenticated requests use the standard Bearer token header:

Authorization: Bearer <JWT_TOKEN>

Only the author of a post can update or delete it.

gRPC API

The gRPC service provides the same core functionality:

Register
Login
CreatePost
GetPost
UpdatePost
DeletePost
ListPosts

Public methods:

Register
Login
GetPost
ListPosts

Authenticated methods:

CreatePost
UpdatePost
DeletePost

JWT tokens for protected gRPC operations are passed through request metadata:

authorization: Bearer <JWT_TOKEN>

The Protocol Buffers schema is defined in:

blog-server/proto/blog.proto

and is used for generating the server and client implementations.

Database

The application stores its data in PostgreSQL.

The main tables are:

users

id
username
email
password_hash
created_at

posts

id
title
content
author_id
created_at
updated_at

posts.author_id references users.id.

Database schema changes are managed through SQLx migrations.

Prerequisites

The project requires:

  • Rust and Cargo
  • PostgreSQL
  • sqlx-cli
  • Protocol Buffers compiler (protoc)
  • wasm-pack
  • Trunk

Install SQLx CLI if necessary:

cargo install sqlx-cli --no-default-features --features postgres

Install wasm-pack:

cargo install wasm-pack

Install Trunk:

cargo install --locked trunk

Environment Configuration

The backend uses environment variables for its configuration.

For local testing, the project uses .env.test.

The database connection used in the examples below is:

postgres://test_user:test_password@localhost:5433/test_db

The environment must also provide the JWT secret expected by the backend.

Do not commit files containing real secrets to Git.

Database Setup

Run database commands from the directory where the migrations directory is available to SQLx.

Reset the database

DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" sqlx database reset

This recreates the database and applies the configured database setup from scratch.

Run migrations

To apply migrations without resetting the database:

DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" sqlx migrate run

Building the Backend

Build the backend with the database URL available to SQLx:

DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" cargo build

Running the Backend

Load the test environment and start the compiled server:

export $(grep -v '^#' .env.test | xargs)
./target/debug/blog-server

The backend starts both the HTTP and gRPC services.

The application exposes:

HTTP API:  localhost:8080
gRPC API:  localhost:50051

Building the WebAssembly Frontend

Build the frontend from the WASM crate:

wasm-pack build --target web

The backend URL used by the frontend is configured in index.html.

Make sure it points to the running HTTP backend before opening the frontend.

Trunk is used for serving the frontend locally and can be installed with:

cargo install --locked trunk

HTTP Usage Examples

Register a user

curl -X POST http://localhost:8080/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "email": "alice@example.com",
    "password": "secret123"
  }'

The response contains the authenticated user and a JWT token.

Login

curl -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "password": "secret123"
  }'

Save the returned token for authenticated requests:

export TOKEN="<JWT_TOKEN>"

Create a post

curl -X POST http://localhost:8080/api/posts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "title": "My first post",
    "content": "Hello from Rust!"
  }'

List posts

curl "http://localhost:8080/api/posts?limit=10&offset=0"

Get a post

curl http://localhost:8080/api/posts/1

Update a post

curl -X PUT http://localhost:8080/api/posts/1 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "title": "Updated title",
    "content": "Updated content"
  }'

Delete a post

curl -X DELETE http://localhost:8080/api/posts/1 \
  -H "Authorization: Bearer $TOKEN"

CLI Usage

The CLI uses the blog-client crate and can communicate with the backend through HTTP or gRPC.

Register

cargo run -p blog-cli -- register \
  --username alice \
  --email alice@example.com \
  --password secret123

Login

cargo run -p blog-cli -- login \
  --username alice \
  --password secret123

Create a post

cargo run -p blog-cli -- create \
  --title "My first post" \
  --content "Created from the CLI"

Get a post

cargo run -p blog-cli -- get --id 1

List posts

cargo run -p blog-cli -- list --limit 20 --offset 0

Update a post

cargo run -p blog-cli -- update \
  --id 1 \
  --title "Updated title" \
  --content "Updated content"

Delete a post

cargo run -p blog-cli -- delete --id 1

gRPC Transport

The CLI can use gRPC instead of HTTP with the --grpc option.

Example:

cargo run -p blog-cli -- --grpc create \
  --title "gRPC post" \
  --content "Created using Tonic"

WebAssembly Frontend

The frontend is implemented with Leptos.

Unlike the native blog-client, the browser frontend communicates directly with the HTTP API.

The currently implemented frontend flow is:

Browser
   │
   ├── Register user
   │       │
   │       ▼
   │    HTTP API
   │       │
   │       ▼
   │    JWT token
   │
   └── Create post
           │
           │ Authorization: Bearer <token>
           ▼
        HTTP API

At the current stage, the Leptos frontend implements registration and authenticated post creation. The remaining backend functionality is available through the HTTP API, gRPC API, client library, and CLI.

Authentication and Security

Passwords are hashed with Argon2 before they are stored in PostgreSQL.

After successful registration or login, the server generates a JWT token identifying the user.

Protected HTTP operations use:

Authorization: Bearer <token>

Protected gRPC operations use the equivalent token in gRPC metadata.

The backend verifies both authentication and post ownership before allowing posts to be modified or deleted.

Error Handling

Application-specific errors are defined with thiserror and converted to transport-specific responses.

Examples include:

Invalid credentials  -> 401 / UNAUTHENTICATED
Unauthorized         -> 401 / UNAUTHENTICATED
Forbidden            -> 403 / PERMISSION_DENIED
Post not found       -> 404 / NOT_FOUND
User already exists  -> 409 / ALREADY_EXISTS
Invalid request      -> 400 / INVALID_ARGUMENT

Logging

The server uses tracing for structured application logging.

The log level can be configured through RUST_LOG, for example:

RUST_LOG=debug ./target/debug/blog-server

About

Rust backend platform with HTTP/gRPC APIs, PostgreSQL, JWT auth, CLI and WebAssembly frontend

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages