Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Blog Project

Origin. Assignment from the Yandex Practicum Rust course (capstone-style): one workspace, four crates sharing a domain layer. Developed locally, published as a snapshot.

A small blog platform in Rust, laid out as a Cargo workspace of four crates:

Crate Type Purpose
blog-server binary Backend serving the same logic over HTTP (actix-web, :8080) and gRPC (tonic, :50051); storage - PostgreSQL.
blog-client library A reusable client with a single BlogClient API over HTTP or gRPC.
blog-cli binary A console client built on blog-client.
blog-wasm cdylib A browser frontend (wasm-bindgen) that talks to the HTTP API.

Architecture

The server follows clean architecture principles:

blog-server/src/
├── domain/          # Pure types and errors (User, Post, AppError)
├── application/     # Business logic (AuthService, BlogService) - shared by HTTP and gRPC
├── data/            # PostgreSQL repositories (parameterized sqlx queries)
├── infrastructure/  # Config, DB pool, JWT, tracing
└── presentation/    # HTTP handlers + middleware (actix), gRPC service (tonic)
  • The HTTP and gRPC layers call the same AuthService / BlogService (wrapped in Arc) - no logic duplication.
  • Passwords are hashed with Argon2; access is protected by JWT (24 hours, with user_id + username in the claims).
  • The gRPC schema lives in a single file, blog-server/proto/blog.proto; the client's build.rs references it by relative path (no copy).
  • protoc is not required on the machine - the build scripts use a vendored binary (protoc-bin-vendored).
  • The server uses sqlx runtime queries, so cargo build works without a database connection.

Requirements

  • Rust 1.75+ (rustup, cargo).
  • PostgreSQL - easiest via Docker (see below). protoc is not needed.
  • For the WASM frontend only:
    • rustup target add wasm32-unknown-unknown
    • cargo install wasm-pack
    • any static file server (for example, Python's http.server).

1. Database

Start PostgreSQL (Docker):

docker run -d --name blog-pg \
  -e POSTGRES_USER=blog -e POSTGRES_PASSWORD=blog -e POSTGRES_DB=blog \
  -p 5432:5432 postgres:16

Migrations are applied automatically on server startup (see blog-server/migrations/).

2. Server configuration

Copy the example env file and adjust the values if needed:

cp blog-server/.env.example blog-server/.env

blog-server/.env:

DATABASE_URL=postgres://blog:blog@localhost:5432/blog
JWT_SECRET=please-change-me-to-a-long-random-secret-min-32-chars

You can generate a strong secret with, for example, openssl rand -hex 32. Do not commit .env (it's in .gitignore).

3. Build the whole project

cargo build --workspace

4. Run the server

cargo run --bin blog-server
# HTTP API  -> http://localhost:8080
# gRPC API  -> http://localhost:50051

5. CLI client

The CLI uses HTTP by default; add --grpc for gRPC and --server <url> to override the address. After register/login, the JWT is saved to a .blog_token file and reused automatically.

cargo run --bin blog-cli -- register --username ivan --email ivan@example.com --password secret123
cargo run --bin blog-cli -- login    --username ivan --password secret123
cargo run --bin blog-cli -- create   --title "My first post" --content "Hello, world"
cargo run --bin blog-cli -- get      --id 1
cargo run --bin blog-cli -- list     --limit 20 --offset 0
cargo run --bin blog-cli -- update   --id 1 --title "Updated title"
cargo run --bin blog-cli -- delete   --id 1

# the same commands over gRPC:
cargo run --bin blog-cli -- --grpc create --title "Via gRPC" --content "..."

6. HTTP API (curl)

# Register (201) -> { token, user }
curl -s -X POST http://localhost:8080/api/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"ivan","email":"ivan@example.com","password":"secret123"}'

# Login (200)
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"ivan","password":"secret123"}' | jq -r .token)

# Create a post (201, authentication required)
curl -s -X POST http://localhost:8080/api/posts \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"title":"Hello","content":"World"}'

# Public read
curl -s http://localhost:8080/api/posts/1
curl -s 'http://localhost:8080/api/posts?limit=10&offset=0'

# Update (200) / delete (204) - require a token and authorship
curl -s -X PUT http://localhost:8080/api/posts/1 \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"title":"Hello (edited)","content":"World"}'
curl -s -X DELETE http://localhost:8080/api/posts/1 -H "Authorization: Bearer $TOKEN"

Endpoints

Method Path Auth Result
POST /api/auth/register 201 {token,user} / 409
POST /api/auth/login 200 {token,user} / 401
POST /api/posts Bearer 201 post
GET /api/posts/{id} 200 post / 404
PUT /api/posts/{id} Bearer 200 / 404 / 403
DELETE /api/posts/{id} Bearer 204 / 404 / 403
GET /api/posts?limit=&offset= 200 {posts,total,limit,offset}

The gRPC service mirrors these operations (Register, Login, CreatePost, GetPost, UpdatePost, DeletePost, ListPosts); write RPCs read the JWT from the authorization: Bearer <token> metadata.

7. WASM frontend

rustup target add wasm32-unknown-unknown        # once
cargo install wasm-pack                          # once

cd blog-wasm
wasm-pack build --target web                     # output in blog-wasm/pkg/
cd ..

python -m http.server 8000                       # serve the workspace root
# open http://localhost:8000

On the page you can register/log in (the JWT is stored in localStorage), see the public feed, and create/edit/delete your own posts. Make sure the server is running on :8080.

Project structure

blog-project/
├── Cargo.toml            # workspace + shared dependency versions
├── index.html            # host page for WASM
├── blog-server/          # HTTP + gRPC backend (clean architecture)
├── blog-client/          # HTTP/gRPC client library
├── blog-cli/             # CLI built on blog-client
└── blog-wasm/            # wasm-bindgen frontend

Notes

  • Cargo.lock, .env, .blog_token, target/, and blog-wasm/pkg/ are in .gitignore.
  • Only unary gRPC is used (no streaming).
  • Logs go through tracing; for verbose output set RUST_LOG=debug.

About

Blog platform as a Rust workspace: actix-web + tonic (HTTP & gRPC), sqlx, JWT/Argon2, shared client lib, CLI, WASM frontend. Course assignment.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages