# Dotpress > A lightweight, decorator-free TypeScript framework for building end-to-end typed backend APIs. ## Guide - [Introduction](https://dotpress.dev/getting-started/introduction.md): Dotpress is a lightweight TypeScript framework for backend APIs. You declare each route with defineRoute(): a path, Zod schemas and a handler. Dotpress validates the request, types it from your schemas and from the path, and runs your app on the HTTP server you choose. Hover req.params: orgId comes from the group, memberId from the route. No annotation, no decorator, no class. - [Installation](https://dotpress.dev/getting-started/installation.md) - [Quickstart](https://dotpress.dev/getting-started/quickstart.md): Build a small tasks API on Express in a few minutes: a first route, validated routes, a returned error, the API reference, and a test. - [Project structure](https://dotpress.dev/getting-started/project-structure.md): Dotpress does not impose any folder layout. This page recommends one that scales, with its reasons, and when to adopt each step. Start small: a structure only pays off once the app needs it. - [How Dotpress works](https://dotpress.dev/concepts/how-it-works.md): Dotpress separates what your API does from the server that runs it. - [Request pipeline](https://dotpress.dev/concepts/request-pipeline.md): Every request goes through the same steps, in the same order, on every adapter. Any step can stop the pipeline with an error or a response: the following steps are skipped, and the response still gets its CORS headers and goes through the onResponse hooks. - [Request context](https://dotpress.dev/concepts/context.md): Handlers and middlewares receive one argument: the context of the request, ctx. Destructure what you need: - [End-to-end typing](https://dotpress.dev/concepts/typing.md): In Dotpress, you write types once, as schemas and paths, and TypeScript infers the rest. No generic to pass to defineRoute(), no interface to keep in sync with a validator. Every snippet of this page is compiled against Dotpress when the site is built: hover the code to see what your editor sees. - [Returned errors](https://dotpress.dev/concepts/errors.md): In Dotpress, an error is a value: a handler or a middleware returns it, like any other result. The control flow stays visible: every way out of the handler is a return, readable from top to bottom. There is no exception to catch elsewhere, and no error class to declare. - [Routes](https://dotpress.dev/routing/routes.md): A route is declared with defineRoute(): a method, a path and a handler. The value returned by the handler is the response. - [Route groups](https://dotpress.dev/routing/groups.md): A group shares a path prefix and middlewares between routes. It organizes an API by resource (/projects), by audience (/admin) or by version (/v2). The route answers GET /admin/users, after requireAdmin. - [Validation](https://dotpress.dev/routing/validation.md): Any input of an API must be checked: it comes from clients you do not control. In Dotpress, each route declares the Zod schemas of its inputs, and the framework: parses and validates the request before the handler runs;answers 400 with every issue found, when the request is invalid;gives the handler the parsed values, typed from the schemas. - [Responses](https://dotpress.dev/routing/responses.md): A handler returns its response. It never writes to the native response object: Dotpress builds the response, on every adapter the same way. - [Errors](https://dotpress.dev/routing/errors.md): Handlers and middlewares return their errors, built with the helpers of @dotpress/core. This page lists them, the errors Dotpress produces itself, and how to report the unexpected ones. For the principles, see Returned errors. - [Middlewares and filters](https://dotpress.dev/routing/middlewares.md): A middleware runs before the handler: to authenticate, check a permission, resolve a tenant, rate-limit. A response filter runs after it, to transform the returned value. - [Bodies and file uploads](https://dotpress.dev/routing/bodies.md): Dotpress reads and parses request bodies itself, the same way on every adapter. Validate them with the body and files schemas of the route (see Validation). - [CORS, cookies and base path](https://dotpress.dev/routing/http-options.md): These options of createApp() adapt the API to where it runs: browsers on other origins, cookies, a reverse proxy. - [Logging and error hooks](https://dotpress.dev/routing/logging.md): Dotpress logs through one logger, given to the app, and gives each request a logger of its own, bound to its request ID. - [Plugins](https://dotpress.dev/routing/plugins.md): A plugin extends an app with a reusable feature: routes, global middlewares, response filters, context keys and lifecycle hooks. The packages of Dotpress are plugins (authPlugin(), openapiPlugin(), mailerPlugin()…), and your app can have its own. - [Testing](https://dotpress.dev/routing/testing.md): A Dotpress app is tested in memory: the memory adapter of @dotpress/testing runs the requests through the real pipeline, with the routes, middlewares, validation and plugins of the app, without opening a port. The tests are fast, and need no mock of the framework. The examples use Vitest; any test runner works. ## Adapters - [Choosing an adapter](https://dotpress.dev/adapters/index.md): An adapter connects your app to an HTTP server. Your routes, middlewares, errors and tests do not depend on it: switching from one adapter to another changes one line of createApp(). - [Express](https://dotpress.dev/adapters/express.md): @dotpress/adapter-express runs your app on Express 4 or 5. - [Fastify](https://dotpress.dev/adapters/fastify.md): @dotpress/adapter-fastify runs your app on Fastify 5, on Node.js and AWS Lambda. - [Hono](https://dotpress.dev/adapters/hono.md): @dotpress/adapter-hono runs your app on Hono 4.13 or later, on Node.js and AWS Lambda. Hono 5 is tested against its release candidate. - [AWS Lambda](https://dotpress.dev/adapters/aws-lambda.md): The Hono and Fastify adapters run your app on AWS Lambda, behind: an API Gateway REST API (payload v1);an API Gateway HTTP API or a Function URL (payload v2);an Application Load Balancer. The routes are the same as on a server: only the entry point changes. ## Auth - [Authentication](https://dotpress.dev/auth/index.md): @dotpress/auth authenticates the users of your API with a username and a password, and keeps them logged in with short-lived access tokens and rotating refresh tokens. It works with any database, through a small data provider, and on every adapter. What it gives you: the routes: login, token refresh, current user, cookie session restore, logout;ctx.user and ctx.auth, typed, in every handler, and guards: requireAuth, requireRole(), requireClient();sessions for browsers in httpOnly cookies, protected against cross-site request forgery;security by default: refresh token rotation with reuse detection, login throttling, constant-time login failures, bcrypt hashing, RFC 9068 access tokens checked on every request. - [Data provider](https://dotpress.dev/auth/data-provider.md): @dotpress/auth stores nothing itself. A data provider, an object of a few functions, reads your users and stores the refresh tokens in your database, with the library of your choice. - [Protecting routes](https://dotpress.dev/auth/protecting-routes.md): The auth plugin reads the access token of every request. When it is valid, ctx.user and ctx.auth are set; otherwise, the request stays anonymous. Guards then decide which routes need a user. - [Sessions and tokens](https://dotpress.dev/auth/sessions.md): A session starts at login, and lives as long as its refresh tokens are renewed. This page covers the tokens, the lifetimes, the logout and the revocation of sessions. - [Cookie mode and CSRF](https://dotpress.dev/auth/cookies.md): For browser applications, keep the tokens in httpOnly cookies. The JavaScript of the page never sees them: a script injected in the page (XSS) cannot steal them, while a token stored in localStorage or in memory can be read and sent anywhere. - [Clients](https://dotpress.dev/auth/clients.md): When one API serves several applications, such as a customer app and a backoffice, declare them as clients. Each token then records the application it was issued for, and routes can be restricted to the tokens of one application. The clientId of the login must be one of allowed: otherwise 400.isUserRoleAllowed(role, clientId) decides whether a user may get a token for this client: here, only admins get backoffice tokens.The token carries a client_id claim, exposed as ctx.auth.clientId.requireClient(...clientIds) answers 403 to the tokens of other clients.Removing a client from allowed rejects its tokens, and its sessions can no longer be refreshed. - [Passwords and brute force](https://dotpress.dev/auth/passwords.md) - [Keys and rotation](https://dotpress.dev/auth/keys.md): Access tokens are signed with an RSA private key (RS256), and verified with its public key. Whoever holds the private key can sign tokens for any user: protect it like your database password, and rotate it. - [Security checklist](https://dotpress.dev/auth/security.md): @dotpress/auth is secure by default: rotating refresh tokens, short-lived access tokens, throttled logins, httpOnly cookies protected against CSRF. Some decisions remain yours. Check this list before going to production. ## Services - [OpenAPI and API docs](https://dotpress.dev/services/openapi.md): @dotpress/openapi builds an OpenAPI 3.1 document from the routes of your app, and serves an interactive API reference with Scalar. No annotation: the paths, parameters, bodies and responses come from the Zod schemas of the routes, and the security from their middlewares. The documentation cannot drift from the code. The document is generated once, when the app is created. - [Emails](https://dotpress.dev/services/mailer.md): @dotpress/mailer sends typed, multilingual emails: templates written in MJML or HTML, with shared layouts, data checked by a Zod schema, and interchangeable providers (SMTP, Amazon SES, Resend, SendGrid, Postmark…). It works in a route, a worker or a script. mjml is only needed for MJML templates: with html templates, it is never loaded. @dotpress/core is only needed for the plugin. - [Events and workers](https://dotpress.dev/services/events.md): @dotpress/events lets your code announce what happened, and lets other code react to it, later, in workers. A route that places an order emits order.placed and answers at once; the confirmation email and the warehouse notification run in a worker, retried on their own when they fail. The code that emits never knows where the listeners run: in the same process during development, in a container reading Redis, or in a Lambda function reading SQS. Changing the transport changes one line. - [Cache and memoization](https://dotpress.dev/services/cache.md): @dotpress/cache provides two tools, for two different problems: Start with the memo: it saves queries without any risk of serving outdated data. Add the cache for values that are expensive to load and change rarely. - [Testing helpers](https://dotpress.dev/services/testing.md): @dotpress/testing runs your app in memory, and checks what it does besides answering: the events it emits, the emails it sends, its Lambda handler. For the basics (the memory adapter, createTestApp()), see Testing. ## Reference - [Application](https://dotpress.dev/reference/application.md): createApp() and the object it returns. For an introduction, see How it works. - [Routes and groups](https://dotpress.dev/reference/routes.md): defineRoute(), createRouteGroup(), createRouter() and defineMiddleware(). For a guide, see Routes, Route groups and Middlewares. - [Request context](https://dotpress.dev/reference/context.md): The ctx object received by middlewares and handlers, and its req and res. For an introduction, see The request context. - [Responses and errors](https://dotpress.dev/reference/responses.md): What a handler returns: values, response helpers and errors. For a guide, see Responses and Errors. - [Plugin API](https://dotpress.dev/reference/plugins.md): definePlugin(), the API given to setup(), and the hooks. For a guide, see Plugins. - [Auth](https://dotpress.dev/reference/auth.md): The settings, routes, errors and contracts of @dotpress/auth. For a guide, see Authentication. - [Events](https://dotpress.dev/reference/events.md): The definitions, options, errors and contracts of @dotpress/events. For a guide, see Events and workers; for each queue, Event workers. - [Mailer](https://dotpress.dev/reference/mailer.md): The definitions, options, providers and errors of @dotpress/mailer. For a guide, see Emails. - [Cache](https://dotpress.dev/reference/cache.md): The options, stores and errors of @dotpress/cache. For a guide, see Cache and memoization. - [Migrate from v1](https://dotpress.dev/reference/migrate-from-v1.md): Dotpress 2 is a rewrite. The style of the API stays (defineRoute(), createRouteGroup(), handlers that return their value), but the framework is no longer built on Express, and there is no compatibility layer. This page lists every change that requires action, in the order of a migration. - [Design decisions](https://dotpress.dev/reference/design-decisions/index.md): These pages record the choices that shape Dotpress, and why they were made: the context, the decision, the alternatives that were set aside, and the consequences, good and bad. They explain why the API looks the way it does, and what to expect from it. They share a few principles: The core depends on no HTTP server. No Express, Fastify or Hono in @dotpress/core.Plugins do not know their adapter. A plugin that needs a specific server is a design smell.Services work on their own. The mailer, the event bus and the cache run in a script or a worker, without an app.Types are inferred, not declared. You write schemas; the types follow.Sensible defaults, everything configurable. - [Errors are returned](https://dotpress.dev/reference/design-decisions/returned-errors.md) - [A declarative registry](https://dotpress.dev/reference/design-decisions/declarative-registry.md) - [No decorators, no DI](https://dotpress.dev/reference/design-decisions/no-decorators.md) - [A transport-agnostic core](https://dotpress.dev/reference/design-decisions/agnostic-core.md) - [No edge runtime in 2.0](https://dotpress.dev/reference/design-decisions/edge.md) ## Examples - [Examples](https://dotpress.dev/examples/index.md): Runnable apps, from one feature to a complete API. Each one is tested by the CI of the repository, and its code is imported in these pages when the site is built: what you read is what runs. Start with the example of your server, then the one of the feature you need. The Tasks API shows how they fit together in a real project. - [Express](https://dotpress.dev/examples/express.md): A small tasks API on Express: typed routes in a group (/tasks), with parameters, query and body validated by Zod schemas;errors returned by the handlers (notFoundError(), conflictError()), documented in meta.responses;an OpenAPI 3.1 document generated from the routes (/openapi.json), and its Scalar API reference (/docs);tests of the app in memory with @dotpress/testing, and on the real Express server. Source: examples/express. - [Fastify](https://dotpress.dev/examples/fastify.md): A small notes API, with its Dotpress routes mounted on an existing Fastify instance, and served by Node.js or by AWS Lambda: Dotpress routes next to a native Fastify route (/health), with a Fastify hook applied to both;one app, two entries: server.ts for Node.js, lambda.ts for AWS Lambda;tests with inject() of Fastify, over HTTP, and on simulated API Gateway events, without an AWS account. Source: examples/fastify. - [Hono](https://dotpress.dev/examples/hono.md): The same notes API on Hono: one app, served by Node.js or by AWS Lambda. native Hono middlewares (secureHeaders()) applied to the Dotpress routes, and a native Hono route (/health);two entries: node.ts with @hono/node-server, lambda.ts with the main entry of the adapter, which has no Node.js server;tests in memory with adapter.fetch() (Web Request and Response), over HTTP, and on simulated API Gateway events. Source: examples/hono. - [Authentication](https://dotpress.dev/examples/auth.md): A private notes API with @dotpress/auth, on Express: sign-up, login, and the access token in the Authorization header, for mobile apps and other services;cookie sessions for browsers: both tokens in httpOnly cookies, protected against cross-site request forgery;refresh token rotation, with the session revoked when a used refresh token comes back;logout of the current device or of every device, and a password change that logs out every device;routes for authenticated users (requireAuth), and for admins (requireRole('admin'));the OpenAPI document and the Scalar page, with the guarded routes marked as secured. Source: examples/auth. - [Events](https://dotpress.dev/examples/events.md): An orders API with @dotpress/events, on Fastify. Placing an order emits an order.placed event; its two listeners run later, in a worker, through a Redis queue (BullMQ): the API answers without waiting for the listeners: it only queues one message per listener;send-order-confirmation logs the confirmation, and notify-warehouse calls a warehouse service over HTTP, with an idempotency key;a listener that throws is retried (3 attempts, exponential backoff), then kept in the failed jobs of the queue;without Redis, an in-memory queue runs the listeners in the API process: nothing to install to try it. Source: examples/events. - [Mailer](https://dotpress.dev/examples/mailer.md): A contact form with @dotpress/mailer, on Hono. Each message sends two emails, written in MJML: a notification to the team, with the visitor as reply-to address: the team answers with the Reply button;an acknowledgement to the visitor, in their language (Accept-Language: fr-CA gets the French version);a shared layout, translated texts, and a text version converted from the HTML one;the message of the visitor escaped in the HTML version;previews of the templates in the browser, in development;SMTP with SMTP_URL, and Mailpit to see the delivered emails in development. Source: examples/mailer. - [Cache](https://dotpress.dev/examples/cache.md): An articles API with @dotpress/cache, on Express, using the two tools of the package: the memo (ctx.memo): a value loaded once per request. The middleware and the handler share the current user, and a list loads the authors of its articles with one query, not one per article;the cache (ctx.cache): values kept across requests and instances. Articles are cached for 10 minutes; editing an article expires it, and renaming a user expires every article at once, with tags;a grace period: when the database fails, an expired article is still served for one more day, instead of an error;Redis with REDIS_URL, the memory of the process otherwise. A fake database records its queries: the tests count them, as you would watch the queries of your database. Source: examples/cache. - [Tasks API](https://dotpress.dev/examples/tasks-api.md): A team task manager: every package of Dotpress assembled in one app, organized as a real project. It runs on Fastify and SQLite, built into Node.js. Source: examples/tasks-api. ## Others - [AWS Lambda](https://dotpress.dev/deployment/aws-lambda.md): On AWS Lambda, the API runs in functions that AWS starts on demand: no server to manage, and a cost that follows the traffic. This page covers the infrastructure; for the handler and the bundle, see the Lambda adapter. - [Event workers](https://dotpress.dev/deployment/event-workers.md): The listeners of @dotpress/events run in workers: processes that read a queue and run the listener of each message. This page covers where to run them, and how to set up each queue. - [Node.js and containers](https://dotpress.dev/deployment/node.md): A Dotpress app on Node.js is a long-running process: a container, a VM, or a platform that runs one (Cloud Run, Fly.io, Railway, Render, Kubernetes…). This page covers what to set up before going to production.