Skip to main content
Goal: #[routes] controllers + PrismaService over SQLx—same mental model as NestJS + Prisma, with Rust macros. Canonical reference: backend-recipes.md — Recipe A.

Cargo features (PostgreSQL)

Enable sqlx-postgres on nestrs-prisma (shown above). Use DATABASE_URL such as
postgresql://USER:PASSWORD@127.0.0.1:5432/myapp.

Bootstrap PrismaModule

prisma_model! CRUD (extra example)

After declaring User with prisma_model!(User => "users", { … }):
Expose GET /users?skip=&take= with ValidatedQuery and optionally add X-Total-Count from count.

REST CRUD route map

Run the repo demo

Paths use 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.
If your app issues JWTs, 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 PrismaErrorHttpException in one place.
PrismaErrorHttpException 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 clamp take 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 an Idempotency-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 with X-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.

Operations checklist

Map PrismaError with HttpException::from where Result<_, PrismaError> flows—see Recipe A § A.14 in the mdBook chapter for full filters.