Skip to main content
In this recipe you’ll build a small Express API that exposes a Post collection over five JSON endpoints — the kind of CRUD surface every web app ends up needing. Along the way you’ll touch most of the Esix surface area you will use every day: find, all, create, save, delete, and paginate.

What You’ll Build

Project Setup

Install the dependencies you’ll need:
Note: Esix reads its connection settings from environment variables. Set DB_URL and DB_DATABASE before starting the server, or rely on the defaults (mongodb://127.0.0.1:27017/ and your project name).

The Post Model

Models in Esix are plain TypeScript classes that extend BaseModel. Public class fields become document properties:
BaseModel automatically provides id, createdAt, and updatedAt, so the model definition stays focused on the fields that are specific to your domain.

Validating Request Bodies

Define a zod schema for each shape of input. CreatePostSchema describes the full payload for POST, and UpdatePostSchema derives a partial version for PATCH so any subset of fields is valid:
z.coerce.number() is exactly what query strings need — values arrive as strings, and the schema converts and validates them in one step.

Wiring Up Express

A single router file is enough to host all five endpoints. Each handler parses its input with zod, then assigns validated fields onto the model one at a time — that way the route is the only place that decides which fields a client is allowed to write:
Two patterns to call out:
  • Validate, then assign one field at a time. Spreading or Object.assign into the model lets a client pass id, createdAt, or authorId and have them silently land in the database. A handful of if (input.x !== undefined) post.x = input.x lines spell out the allowlist explicitly.
  • Wrap JSON responses in a root key. { post }, { posts, pagination }, { user, token } — a single top-level field gives you room to add metadata (errors, warnings, links) later without breaking clients.
Mount the router on an Express app:
Note: CreatePostSchema.parse(...) throws a ZodError when validation fails. Add an Express error handler that converts those into a 400 JSON response ({ error, issues }) so clients get a useful message instead of a stack trace.

Trying It Out

Create a post:
The response includes the generated id and timestamps under a post root key:
Fetch the list:

A Few Notes on Patterns

  • Filtering at the list endpoint. The example only returns published posts. Move the filter behind a query parameter when you need an admin view that shows drafts too — see the Search and Filtering recipe for a complete example.
  • One schema per shape. CreatePostSchema and UpdatePostSchema describe the two write shapes the API accepts. partial() keeps the PATCH schema in sync with the create schema for free — add a field once and both endpoints pick it up.
  • find never throws on bad input. Pass any string and you’ll either get a model back or null, which keeps your route handlers tidy.

What’s Next