The async shift: services and controllers
The in-memory services were synchronous. Pure functions returning arrays. The moment you swap in a database, everything that touches it becomes async. The query goes over a network connection, waits for Postgres to answer, and resolves a Promise. Your service signatures change. Your controllers change. The structure of the code stays the same.
export async function getBooksHandler(
req: Request,
res: Response,
next: NextFunction
) {
try {
const books = await bookService.getAllBooks();
res.json(books);
} catch (err) {
next(err);
}
}
export async function createBookHandler(
req: Request,
res: Response,
next: NextFunction
) {
const validation = CreateBookSchema.safeParse(req.body);
if (!validation.success) {
res.status(400).json({ error: 'Invalid book data', details: validation.error.format() });
return;
}
try {
const newBook = await bookService.createBook(validation.data);
res.status(201).json(newBook);
} catch (err) {
next(err);
}
}Same controller, now async with try/catch passing errors to next.
The pattern that matters: wrap every async controller body in try/catch and call next(err) on failure. Express then routes the error to the error-handling middleware you wrote earlier. Without try/catch, an unhandled rejection crashes the process. With it, every error has one place to be handled.
You send a Promise object as the JSON body, and the client gets back the empty object {}. It is the most quietly painful bug in async Node, because the error message is unhelpful and the data shape looks almost right. Whenever a response has unexpected empty fields, suspect a missing await.
Quiz: Quiz
Loading practice…
Validation checklist: Persist a book and prove it
Loading practice…
Take a second to look at what you have built. A validating CLI, a working HTTP server, a layered REST API, and a Postgres-backed persistence layer. This is the entire foundation of every backend in production. Everything that comes next (auth, queues, caching, websockets, observability) hangs off this skeleton.
Checkpoint: Foundations checkpoint
Loading practice…