
Backend Service
An e-commerce backend with no interface: 15 route groups, 18 Prisma models, identity through JWT and refresh tokens, provider sign-in, rate limiting, an audit trail and a metrics endpoint. The contract is generated from Swagger and the service is packaged with Docker.
In an application with an interface, the answer to "is it working" is the screen. In a service with no interface there is no such answer: the service can be up and behaving wrongly, and only the consumer notices — usually late. So the real problem here is not business logic but contract and visibility. Second, in an e-commerce API a repeated request is ordinary: the network drops, the client retries, the payment provider sends the same notification a second time. If "what happens if this is processed twice" is answered separately at every endpoint, one endpoint eventually gets forgotten.
Two kinds of consumer. The direct client — a web or mobile interface: reads the catalogue, writes carts and orders, manages sessions. The server-side consumer — a payment or shipping provider: sends webhooks, proves its identity with a shared secret, and may repeat the same notification. There is a third party as well: whoever operates the service, whose need is not a screen but logs, metrics and an audit trail.
Single developer. Data model and migrations, the middleware chain, identity and authorisation layer, order state machine, audit and observability layer, the Swagger contract and the Docker package.
A single service on Express; a request passes through a fixed middleware chain: security headers, resource-sharing policy, request identifier, rate limiting, JWT verification, role check, ownership check and Zod schema validation. At the end of the chain the controller only runs business logic; error catching and the shape of the error body are collected in a single error middleware, so every endpoint produces the same error envelope. Order status is not inside controllers but defined in a separate state machine module — which state can move to which lives in one place. Repeated requests are handled by a dedicated idempotency middleware, so no endpoint invents its own solution. On the data side, Prisma and PostgreSQL; the schema carries a separate search vector column for product search, and listing endpoints use cursor-based pagination rather than page numbers. Observability has three parts: structured logs, request logging and a metrics endpoint.
A repeated request is not specific to one endpoint; adding to a cart, creating an order and processing a webhook share the same problem. Once the solution is in middleware, no endpoint can forget it — the protection is attached to the route definition, not to a controller's discipline.
Trade-offThe client has to generate an idempotency key and send the same key on retry; a client that does not cannot benefit from the protection. The store of processed keys also grows continuously and has to be cleaned up.
The order lifecycle is where the system branches most: payment, preparation, shipping, delivery, cancellation and returns. If the transition rules are scattered across controllers, the same rule gets written differently in several places and drifts over time. Collecting them in one module means valid transitions can be read from one place.
Trade-offThe rule lives in the application, not the database: an update made directly in SQL can bypass the state machine. Just as I reduced the overlap rule to a database constraint in Ustura, transitions could have been enforced at the data level here; they were not.
The access token is short-lived and stateless; the refresh token exists as a record in the database. The reason is revocability: a stateless refresh token stays valid until it expires, leaving no way to close a stolen session. Provider sign-ins are also linked through a separate account model, so one user can carry more than one sign-in method.
Trade-offEvery refresh request means a database read — the scaling advantage of a stateless design is deliberately given up here. The table also grows over time and expired records have to be cleaned up.
The metrics endpoint describes the system's internal state and the Swagger interface describes the whole contract. Both are reconnaissance material for an attacker. Both were put behind authentication; the webhook endpoint is additionally verified with a shared secret.
Trade-offClosed documentation means an extra access step for a developer who wants to consume the API. Protecting the metrics endpoint also requires separate configuration on the collector side — it does not work directly with a collector that does not support authentication.
Writing a request body to the log makes debugging easier, but the same body can contain a password, a token or an address. Redaction happens at write time and per field; a sensitive field never enters the log rather than being cleaned up afterwards.
Trade-offThe list of fields to redact is maintained by hand; a new sensitive field has to be added to it and nothing reminds you to do so. A redacted field is also invisible while debugging, so some errors cannot be traced through the log.
Express was chosen so the middleware chain stays readable: what a request passes through, and in what order, can be followed by reading the code — and that means security decisions stay visible. Prisma keeps the relations and migration history of an 18-model schema traceable; the schema file doubles as documentation for the data model. Zod collects request-body validation in a single middleware while also producing types from the same schema. The Swagger contract is written next to the code as comments — which makes it harder for a separate document to drift away from the code. Docker makes the service a single runnable unit together with its dependencies; in a service with no interface, "it works on my machine" means nothing.
The middleware chain starts with security headers and the resource-sharing policy is explicitly defined. Rate limiting is configured per endpoint and is tighter on identity endpoints. Passwords are stored with bcrypt, the access token is short-lived, and because the refresh token is kept in the database it can be revoked. Authorisation has three levels: authentication, role check and resource ownership check — so even a user with the right role cannot edit someone else's record. The webhook endpoint is verified with a shared secret. File uploads pass through a separate middleware. Metrics and Swagger endpoints sit behind authentication. Changes are written to a separate audit log model; log fields are redacted at write time.
This is a backend service; a page-speed measurement is meaningless and there is no stored load-test output, so no number is written here. Structurally there are three decisions: listing endpoints use cursor-based pagination, so query cost stays constant on deep pages; a separate search vector column is kept in the schema for product search, so search does not scan text on every request; and because the metrics endpoint exposes values such as request count and duration, the service's behaviour can be followed by measurement rather than guesswork.
There are no automated tests in this repository, and for a backend service that is the most expensive gap. The easiest and most valuable place to test is obvious: the order state machine is already a pure module, testable as input and output, and it carries the most branching rule in the system. Today I would start there, then verify the idempotency middleware with the same request sent twice. The second point is where the state rule lives: I kept transitions in the application, whereas in Ustura reducing the overlap rule to a database constraint applied the protection to every write path at once. Here an update made directly in SQL can bypass the state machine; today I would at least add a check constraint preventing an invalid state.
Tell me what you want to build; I'll tell you up front how long it takes and where to start.