Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24, Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login, self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the blog series, safe markdown links in the frontends, and many smaller bugs — every project was built, run and exercised end to end. Adds scripts/publish.sh + a CI publish job that splits every folder into its own repo at git.devai.io/templates/<folder>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
6.4 KiB
rust-crud-nosql-api
The JWT-secured REST API from rust-crud-sql-api, on MongoDB: warp filters, the
official mongodb driver, argon2 passwords and role-based routes (User / Admin)
— users, articles and comments, laid out as routes → handlers → service per module.
Run
git clone https://git.devai.io/templates/rust-crud-nosql-api.git
cd rust-crud-nosql-api
docker compose up --build
The API answers on http://localhost:8080 (curl localhost:8080/health → ok).
MongoDB keeps its state in ./data/mongo; the unique indexes on users.email and
articles.url are ensured on every start, so there is no migrate step.
Without Docker: run a MongoDB, export the variables from .env.example, then
cargo run (Rust 1.98, the toolchain the Dockerfile pins).
How it works
| Method | Path | Auth | Result |
|---|---|---|---|
| GET | /health |
— | 200 ok |
| POST | /api/auth/register |
— | {email, name, password} → 201 user (role User), 409 if taken |
| POST | /api/auth/login |
— | {email, password} → 200 {id, email, name, role, access_token} |
| GET | /api/articles |
— | 200 all articles, without content |
| GET | /api/articles_home |
— | 200 articles with in_home: true, without content |
| GET | /api/articles/{url} |
— | 200 full article, or 404 |
| POST | /api/articles |
Admin | {title, url, content?, tags?, in_home?} → 201, 409 if the url exists |
| PUT | /api/articles |
Admin | same body plus id → 200, or 404 |
| DELETE | /api/articles/{id} |
Admin | 204 (its comments go with it), or 404 |
| PUT | /api/articles/updateHomeView/{id} |
Admin | flips in_home → 200 article |
| GET | /api/articles/comments/{article_id} |
— | 200 comments, oldest first |
| POST | /api/articles/comments |
— | {article_id, author, email, content} → 201, 404 for an unknown article |
| DELETE | /api/articles/comments/{article_id}/{id} |
Admin | 204, or 404 |
| GET | /api/users |
Admin | 200 all users |
| GET | /api/users/{id} |
Admin | 200 user, or 404 |
| POST | /api/users |
Admin | {email, name, password, role?} → 201 |
| PUT | /api/users |
Admin | {id, email, name, role} → 200 |
| DELETE | /api/users/{id} |
Admin | 204, or 404 |
| PUT | /api/users/changePassword |
any | {id, current_password?, new_password} → 204 |
-
Auth — login returns an HS256 JWT signed with
AUTH_SECRET(sub= user id,role, 24 h expiry); send it asAuthorization: Bearer <token>. Verification pins HS256 and requiresexp.with_auth(env, Role::Admin)insrc/auth/middleware.rsis the warp filter that answers401without a valid token and403when the role is not enough. -
Passwords — argon2id (RustCrypto
argon2), hashed on tokio's blocking pool. Users change their own password withcurrent_password; admins can reset anyone's. -
Roles — registration always creates a
User. Promote the first admin in the database, then log in again (the role travels inside the JWT):docker compose exec db mongosh demo --eval \ 'db.users.updateOne({email: "admin@test.com"}, {$set: {role: "Admin"}})' -
Comments live inside their article document (
$push/$pull). They are public to read and write; the commenter's email is stored but never returned. -
Ids are MongoDB ObjectIds, serialized as hex strings.
-
Errors are always JSON:
{"error": "message"}, including warp's own rejections (bad JSON400, wrong content-type415, wrong method405).
Try it:
curl -X POST localhost:8080/api/auth/register -H 'content-type: application/json' \
-d '{"email":"admin@test.com","name":"Admin","password":"supersecret"}'
# promote it with the mongosh command above, then:
TOKEN=$(curl -s localhost:8080/api/auth/login -H 'content-type: application/json' \
-d '{"email":"admin@test.com","password":"supersecret"}' | jq -r .access_token)
curl -X POST localhost:8080/api/articles -H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' -d '{"title":"Hello","url":"hello","content":"First post"}'
curl localhost:8080/api/articles/hello
Layout
src/main.rs route tree, request log, graceful shutdown
src/environment.rs config, MongoDB collections, unique indexes, shared filters
src/error.rs ApiError and the rejection → {"error": ...} mapping
src/auth/ JWT + argon2 (mod.rs), with_auth filter, register/login
src/users/ routes → handlers → service (MongoDB queries)
src/articles/ routes → handlers → service, embedded comments included
Deploy
Push to your own GitHub repo and the shipped workflow
(.github/workflows/ci.yml) tests the compose stack, publishes the image to
GHCR, and — once you set the DEPLOY_HOST / DEPLOY_USER variables and
DEPLOY_KEY secret — deploys it to your server over ssh. Set a long random
AUTH_SECRET on the server; the one in compose.yaml is for local use only.
Part of devai.io — Rust API boilerplates. Same API on Postgres:
rust-crud-sql-api; actix-web take:
rust-crud-actix-mongo-api.