Module F-8·22 min read

Building a complete CRUD REST API end-to-end — project structure, error handling, dotenv, nodemon, and testing with curl and Postman.

Module F-8 — Your First Real Application

What this module covers: Modules F-1 through F-7 gave you all the individual pieces. This module assembles them into a real, structured project. We build a Blog API — full CRUD for posts and users, proper project structure, environment configuration, error handling, and a working Prisma database layer. By the end you will have a template that reflects how real Node.js applications are organised, and you will understand why each layer exists.


What We Are Building

A Blog API with two resources:

  • Users — register, read profile
  • Posts — create, read, update, delete blog posts

Endpoints:

text

This is deliberately simple — no authentication yet (that's P-2). The goal is clean project structure and solid fundamentals.


Project Structure

text

This is the layered architecture we will formalise in P-1. For now the pattern is: routes handle HTTP, the db/ layer handles data access, and middleware/ holds cross-cutting concerns.


Step 1: Project Initialisation

bash

Update package.json:

json

Create .gitignore:

text

Create .env:

text

Create .env.example (committed — shows what vars are needed without exposing values):

text

Step 2: Database Schema

prisma

Run the migration:

bash

This creates the tables in your PostgreSQL database and generates the Prisma Client.


Step 3: Prisma Client Singleton

javascript

One PrismaClient instance for the entire application. Creating one per request wastes connections and causes pool exhaustion.


Step 4: Route Handlers

javascript
javascript

Step 5: Error Handler Middleware

javascript

Step 6: Application Entry Point

javascript

Step 7: nodemon for Development

json
bash

Nodemon watches src/ and restarts the server whenever you save a file.


Testing the API

bash

What This Structure Gives You

Looking at the project layout again:

text

Why separate db/ from routes/? Your route handlers are now thin — they validate input, call a function, and send a response. All database logic is in db/. If you switch from Prisma to raw SQL tomorrow, you change db/ only. Routes stay the same.

Why a dedicated error handler? All errors flow to one place. You change error formatting once. You add logging once. Every route benefits automatically.

Why dotenv/config at the top of index.js? Environment variables must be loaded before anything else reads them. Loading it first in the entry file ensures process.env.DATABASE_URL is available when prisma.js is imported.


What Comes Next

This application is the baseline. As you move into the Practitioner phase, you will add:

  • P-1: Proper layered architecture (service layer between routes and DB)
  • P-2: JWT authentication — protect routes, know who is making requests
  • P-3: TypeScript — full type safety from route parameters to database results
  • P-4: Zod validation — replace manual if (!title) checks with schema declarations
  • P-5: Tests — Jest and Supertest to verify every endpoint automatically
  • P-8: Docker — containerise the entire app and database

Each addition builds on the structure you have here. The fundamentals do not change — they get reinforced.


Summary

  • A real application has layers: routes (HTTP), services (logic), and data (database). Even this small app separates HTTP handling from database access.
  • dotenv/config loads environment variables first, before any imports that need them.
  • One Prisma client instance per application. Never create one per request.
  • Pass errors to next(err) in every route handler. The global error handler catches them all.
  • A health check endpoint (GET /health) is a small addition that makes your app ready for Docker and Kubernetes load balancer checks.
  • .env.example is committed; .env is not. Everyone on the team knows what configuration the app needs.

Next: Event Emitters — the mechanism underlying everything in Node.js from HTTP servers to file streams.


Knowledge Check

Why is it important to create a single PrismaClient instance and export it as a singleton rather than instantiating it inside route handlers?


In the context of the blog API's error handling, why do the route handlers catch errors and pass them to next(err)?


Why is .env.example committed to version control while .env is added to .gitignore?

Test your knowledge with more question sets

Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.