Meet ZudoJS Core
Welcome to ZudoJS
What ZudoJS is, how its packages are layered, what each of its packages is for, what the zudojs command-line tool does, and the three application shapes it can create. Then run three ZudoJS packages together in your browser.
What ZudoJS is
ZudoJS is a backend framework for TypeScript on Node.js. It takes over the jobs you listed in What a framework does: lifecycle, dependency injection, routing, configuration, validation, errors, data access, security, testing and project structure.
It is not one big package. It is 38 small npm packages, each named @zudojs/something, plus a command-line tool published as zudojs. Each package owns one job. You install only the ones you use, and you can use most of them on their own, even in a project that is not a ZudoJS app.
That design follows the ideas from Backend architecture: separation of concerns, interfaces between parts, dependency injection, and dependencies that point in one direction only.
Packages on shelves
Think of the packages as sitting on shelves. A package may use packages on lower shelves, never higher ones. So there are no cycles, and each package can be built, tested and understood on its own.
top testing fakes and helpers for all the others
http, cli talk to the outside world
runtime, auth, cqrs, … put the parts together into an application
core, events, schema, … framework machinery
config, logger, … single-purpose building blocks
bottom errors, types depend on no other ZudoJS package
- At the bottom is
@zudojs/errors. Almost every other package uses it, so an error from any package is aBaseErrorwith a status code and a stablecode.@zudojs/typessits beside it with shared type guards. - In the middle are building blocks such as
@zudojs/config,@zudojs/logger,@zudojs/containerand@zudojs/schema. For example,@zudojs/schemauses onlyerrors,constantsandtypes. - Higher up are the packages that combine others:
@zudojs/runtimestarts and stops an application, and@zudojs/httpserves it on the network.
The Architecture and Dependency direction pages list every package's exact dependencies.
The packages, grouped by job
You do not need to remember this table. Come back to it when a lesson mentions a package. Each name links to its documentation.
| Job | Packages |
|---|---|
| Foundations: errors, shared types and constants, checking data | errors, types, constants, schema, validation, serialization |
| Application core: modules, start and stop, dependency injection, settings, logs | core, runtime, lifecycle, container, config, logger, plugins |
| HTTP and APIs: serving requests, and describing and calling APIs | http, middleware, api, rpc, openapi, adapters |
| Data: databases, storage, transactions, caching | database, storage, transactions, cache |
| Users and security: log-in, permissions, protection, cryptography | auth, auth-oauth, permissions, security, crypto, tenancy |
| Events and background work | events, messaging, cqrs, queue, scheduler |
| Running in production: seeing inside, switching features, testing | observability, feature-flags, testing |
| Tools: creating projects and documentation | zudojs (the CLI), docs |
A normal application starts with about a dozen of them. The rest arrive when you need them: queue when you have background jobs, tenancy when you serve several customers from one app, and so on. The package list has a search box.
How the packages fit together
Here is the path one request takes through a ZudoJS application, with the package that handles each step. Compare it with the BookStore, where you wrote every step yourself:
| Step | Package | BookStore equivalent |
|---|---|---|
Start every part in order, stop in reverse on SIGTERM | runtime, core | (missing) |
| Read and check the settings | config | config.ts |
| Accept the request, run middleware | http, security | app.ts, router.ts, json.ts |
| Find out who is asking, and whether they may | auth, permissions | requireUserId |
| Check the body | schema | validation/ |
| Hand the controller its service | container | createRouter |
| Load and save data | database, transactions | repositories/, db.ts |
| Turn a failure into a status code | errors | errors.ts, toErrorResponse |
| Write what happened | logger, observability | console.log |
The packages that do not need Node.js also run in this page's browser terminal. Here are three of them working together: @zudojs/schema checks the input, @zudojs/errors reports failures, and @zudojs/container hands out the one shared service:
import { ContainerScope, createContainer, createToken } from "@zudojs/container";
import { BaseError, NotFoundError } from "@zudojs/errors";
import { schema } from "@zudojs/schema";
import type { Infer } from "@zudojs/schema";
const NewBookSchema = schema.object({
title: schema.string().trim().min(1).max(200),
priceCents: schema.number().int().min(0),
});
type Book = Infer<typeof NewBookSchema> & { readonly id: number };
class BookService {
private readonly books: Book[] = [];
add(input: unknown): Book {
const book = { id: this.books.length + 1, ...NewBookSchema.parse(input) };
this.books.push(book);
return book;
}
get(id: number): Book {
const book = this.books.find((b) => b.id === id);
if (book === undefined) throw new NotFoundError(`Book ${id} not found`);
return book;
}
}
const BOOKS = createToken<BookService>("books");
const container = createContainer();
container.register(BOOKS, { useFactory: () => new BookService() }, { scope: ContainerScope.SINGLETON });
const books = container.resolve(BOOKS);
console.log(books.add({ title: " Kindred ", priceCents: 1099 }));
console.log("same service everywhere:", container.resolve(BOOKS) === books);
for (const attempt of [() => books.add({ title: "", priceCents: -1 }), () => books.get(9)]) {
try {
attempt();
} catch (error) {
if (error instanceof BaseError) console.log(error.name, error.statusCode, error.code, error.message);
}
}
npx tsx books.ts and of the browser terminal{ id: 1, title: 'Kindred', priceCents: 1099 }
same service everywhere: true
SchemaError 400 ERR_SCHEMA_VALIDATION Validation failed
NotFoundError 404 ERR_RESOURCE_NOT_FOUND Book 9 not foundPress Run in browser and change things. What to notice:
- One description, two uses.
NewBookSchemachecks the data at runtime, andInferturns it into theBooktype. In the BookStore you wrote the interface and the parser separately. - The container creates
BookServicethe first time someone asks for theBOOKStoken, and gives everyone that same instance (SINGLETON). NocreateRouterthreading objects through by hand. - One error family. The
SchemaErrorthrown by@zudojs/schemaand theNotFoundErroryou threw both extendBaseErrorfrom@zudojs/errors, so oneinstanceofcheck reads the status code of either.
The next lesson installs @zudojs/schema and @zudojs/errors on your computer and explains them in detail. The container gets its own lesson in the ZudoJS core part.
The command-line tool
Choosing a dozen packages and wiring them into a project by hand would bring back the problem you just left behind. So ZudoJS has a CLI (command-line interface), published on npm as zudojs. It gives you a command called zudojs, and a short name for it, zudo. Run on its own, it shows a menu of these commands:
| Command | What it does |
|---|---|
zudojs create | Creates a new project: folders, packages, scripts, a server with security defaults, an example endpoint and tests. |
zudojs dev | Starts the development server, restarting when you save. |
zudojs build | Compiles the project to JavaScript for production. |
zudojs generate | Adds a complete endpoint (generate resource), or a single service, controller, module and more, in the right folder, and wires it in. |
zudojs add | Adds a feature such as database, redis or docker to an existing project: the code, the settings and the package. |
zudojs doctor | Checks your Node.js, packages and project for problems. |
zudojs info | Shows the CLI version and the project's ZudoJS packages. |
You will install it and use every one of these in Create the Task API project.
Three shapes of application
zudojs create asks which architecture you want. There are three, and they differ in how many deployable programs you end up with:
| Architecture | What it is | Choose it when |
|---|---|---|
| Monolith | One application, one codebase, one process. Like the BookStore. | You are starting out, or the team is small. This is the default and what this course uses. |
| Modular monolith | Still one deployable application, but split into strict modules (say, catalog, orders, users) that talk through clear interfaces instead of reaching into each other's code. | The app has grown and you want clear boundaries, without the cost of running many services. |
| Microservices | Several separate applications, each with its own data, talking over the network, usually behind a gateway. | Different parts must scale or be deployed independently, by different teams. It adds a lot of operational work. |
A common and healthy path is monolith → modular monolith → microservices only where needed. The same ZudoJS packages work in all three, and the Architecture with ZudoJS part of the course walks that path.
Practice
TRY IT YOURSELF
Which package?
For each need, name the ZudoJS package: (a) send a welcome e-mail in the background after sign-up; (b) let only admins delete books; (c) read DATABASE_URL and fail if it is missing; (d) count requests per second on a dashboard.
Show a solution
(a) @zudojs/queue, for background jobs. (b) @zudojs/permissions, for who may do what. (c) @zudojs/config. (d) @zudojs/observability, for metrics.
TRY IT YOURSELF
Look up a missing book
Change the example so that BookService also has findByTitle(title), which throws NotFoundError when no book has that exact title. Try it with a title that exists and one that does not.
Show a solution
import { NotFoundError } from "@zudojs/errors";
interface Book {
readonly id: number;
readonly title: string;
}
const books: Book[] = [{ id: 1, title: "Kindred" }];
function findByTitle(title: string): Book {
const book = books.find((b) => b.title === title);
if (book === undefined) throw new NotFoundError(`No book called "${title}"`);
return book;
}
console.log(findByTitle("Kindred"));
try {
findByTitle("Dune");
} catch (error) {
if (error instanceof NotFoundError) console.log(error.statusCode, error.message);
}
npx tsx find-by-title.ts and of the browser terminal{ id: 1, title: 'Kindred' }
404 No book called "Dune"Recap
- ZudoJS is 38 small
@zudojs/*packages plus thezudojsCLI. Each owns one job, and you install only what you use. - Packages sit on shelves and only use packages below them.
@zudojs/errorsis at the bottom, so every error shares one base class. - A request passes through
http,security,auth,schema,container,databaseanderrors, andruntimestarts and stops it all. - The
zudojsCLI (short namezudo) creates, runs, builds, extends and checks projects. - Applications can be a monolith, a modular monolith or microservices. Start with a monolith.
Next, you install your first two ZudoJS packages and use them in a Task API service.
Test yourself
Five questions, picked at random from this lesson's question bank. Some ask you to choose an answer, some to predict what code prints, and some to write code and run it in the terminal. Get 4 of 5 right to pass. If you don't, read the explanations and try again: you get 5 different questions.