Skip to content
On this page

Zod example

In the basic example, we learned how to create a basic user system using Neboa and Express. However, we didn't do any validation on the user data.

In this example, we'll use Zod to manage our types and validate our data. This will allow us to create a more robust CRUD system.


First, we'll install the required dependencies:

npm install neboa
npm install express
npm install body-parser
npm install zod

We'll use the following file structure:

├── src
│   ├── index.ts                # Entry point of the application
│   ├── database
│   │   └── database.ts         # Database connection
│   │   └── schemas
│   │       └── user-schema.ts  # User schema

Creating the database

We'll start by creating a schema for our users:

// src/database/schemas/user-schema.ts
import { z } from 'zod';
import { NeboaDocument } from 'neboa';

export const UserSchema = z.object({
  username: z.string().min(3).max(20),
  email: z.string().email(),
  password: z.string().min(8),

export type User = z.infer<typeof UserSchema>;
export type UserDocument = NeboaDocument<User>;

Next, we'll create a database connection and add a users collection to it using our User type inferred from the zod schema:

// src/database/database.ts
import { neboa } from 'neboa';

export const db = neboa('path/to/database.sqlite');
export const Users = db.collection<User>('users');

After this, we have the basics of our database connection handled and we can create some api routes to interact with it.

Creating the API

We'll start by creating a simple route to create a user:

// src/index.ts

import express from 'express';
import bodyParser from 'body-parser';
import { Users } from './database/database';
import { UserSchema, UserDocument, User } from './database/schemas/user-schema';

const app = express();
app.use(bodyParser.json());'/api/users', async (req, res) => {
  const result = UserSchema.safeParse(req.body);
  if (!result.success) return res.status(400).json(result.error);

  const user: User =;
  try {
    const newUser: UserDocument = Users.insert(user);
  } catch (err) {
    res.status(500).json({ error: err.message });

app.listen(3000, () => console.log('Server listening on port 3000'));

We've added a few things to our route:

  • We're now validating the user data using our zod schema with UserSchema.safeParse().
  • We're returning a 400 error if the data is invalid with an explanation of the error generated by zod.
  • We're returning a 500 error if the data is valid but the insert fails.
  • We're returning the newly created user if the insert succeeds.

The same method can be applied for a PUT route to update a user:

// src/index.ts

app.put('/api/users/:id', async (req, res) => {
  const result = UserSchema.safeParse(req.body);
  if (!result.success) return res.status(400).json(result.error);

  const user: User =;
  try {
    const updatedUser: UserDocument = Users.update(, user);
  } catch (err) {
    res.status(500).json({ error: err.message });

And that's how simple it is to integrate zod with Neboa!

Released under the MIT License.