#[routes] controllers + PrismaService over SQLx—same mental model as NestJS + Prisma, with Rust macros.
Canonical reference: backend-recipes.md — Recipe A.
Cargo features (PostgreSQL)
- PostgreSQL
- SQLite (hello-app)
- MySQL
Enable
sqlx-postgres on nestrs-prisma (shown above). Use DATABASE_URL such aspostgresql://USER:PASSWORD@127.0.0.1:5432/myapp.Bootstrap PrismaModule
prisma_model! CRUD (extra example)
After declaring User with prisma_model!(User => "users", { … }):
GET /users?skip=&take= with ValidatedQuery and optionally add X-Total-Count from count.
REST CRUD route map
Run the repo demo
set_global_prefix("platform") + controller version + prefix—see examples/hello-app/src/main.rs in the repo.
Production-grade REST CRUD
These patterns mirror what teams ship behind API gateways (Kong, Envoy): predictable errors, bounded queries, and observability hooks—not “demo CRUD.”End-to-end example: SaaS users API
This is the kind of CRUD surface teams actually expose from an admin or back-office service: tenant-aware, validated, paginated, and conflict-safe.tenant_id should come from the verified token or a guard, not from user input. That is the difference between “works locally” and “safe in production.”
Layering (controller → service → data)
Keep#[routes] thin: parse/validate HTTP, delegate to an #[injectable] service that owns Arc<PrismaService>. That keeps transaction boundaries and mapping PrismaError → HttpException in one place.
PrismaError → HttpException maps unique violations to ConflictException (409), missing rows to NotFoundException (404), pool issues to ServiceUnavailableException (503). Surface those directly instead of wrapping everything as 500.
Pagination and list safety
Always clamptake on the server (for example max 100) and default skip to 0. Return total row counts only when you need faceted UIs—otherwise count on large tables can dominate latency; consider approximate counts or cursor-based paging for very large lists.
Expose query params through ValidatedQuery so invalid types never reach Prisma.
Idempotent creates (payments, orders)
For POST endpoints that must not double-charge when the client retries, accept anIdempotency-Key header (standard pattern). Store (tenant_id, key) → response in Redis or a dedicated SQL table with a TTL; on replay, return the stored body and status without calling create again. nestrs does not ship a built-in idempotency middleware—you implement storage + lookup in your UserService (or a small IdempotencyStore injectable).
Headers and versioning
Pair list responses withX-Total-Count only when clients require exact totals; otherwise prefer Link RFC 5988-style rel="next" for cursor or offset paging. Keep Api-Version or URI versioning aligned with #[controller(version = …)] so deprecations are explicit.