Node and Express are the runtime and HTTP framework for your API. Topics here cover bootstrapping the app, parsing JSON bodies, and structured logging—everything that runs before a route handler executes. Master this layer first so later routers and controllers plug into a consistent foundation.

Node / Express

Node and Express are the runtime and HTTP framework for your API. Topics here cover bootstrapping the app, parsing JSON bodies, and structured logging—everything that runs before a route handler executes. Master this layer first so later routers and controllers plug into a consistent foundation.

Select a function type from the sidebar, or browse topics below.

How to initialize an Express application — Basic

Create the Express app instance and register core middleware before defining routes.
Learn: Every Express API starts with a single app instance. Register global middleware (JSON parsing, logging, CORS) before routes so every request is prepared the same way. Keep app.js focused on wiring; move route logic into routers as the project grows.

Supplementary examples

Minimal app with a health check

const express = require("express");
const app = express();

app.get("/health", (req, res) => res.json({ ok: true }));

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Listening on ${PORT}`));

Register JSON parsing before routes

const express = require("express");
const app = express();

app.use(express.json()); // must run before handlers that read req.body

Course example

{
"name": "deploying-the-server",
"version": "1.0.0",
"engines": {
"node": "12.13.0"
},
"description": "",
"main": "server.js",
"scripts": {
"start": "node server",
"server": "nodemon server.js",
"client": "npm start --prefix client"
},
"keywords": [],
"dependencies": {
"express": "^4.17.1",
"cors": "^2.8.5"
},
"devDependencies": {
"nodemon": "^2.0.3"
}
}

Additional references & examples

How Promises work in backend code — Basic

A Promise represents async work that will finish later (database, file, network). Knex queries return promises; Express handlers use async/await to read results cleanly.
Learn: Before async/await, Promises were the standard way to handle async results in Node. Knex still returns a Promise from every query builder chain. Understanding then/catch helps you read older examples; writing new code with async/await is usually clearer in controllers and services.

Supplementary examples

Promise.all for parallel queries

const [users, pastes] = await Promise.all([
  knex("users").select("*"),
  knex("pastes").select("*"),
]);

Course example

// Knex returns a Promise
const promise = knex("users").select("*");

promise
  .then((rows) => console.log(rows))
  .catch((err) => console.error(err));

// Same query with async/await (preferred in controllers/services)
async function listUsers() {
  const rows = await knex("users").select("*");
  return rows;
}

Additional references & examples

How to add HTTP request logging — Basic

Register pino-http (or similar) on the app to log each incoming request.
Learn: Request logging answers “what hit the server?” in production. pino-http attaches a logger to each request so handlers can log with context. Place it early in the middleware stack, after body parsers if you log POST bodies sparingly.

Supplementary examples

Attach pino-http to the app

const pinoHttp = require("pino-http");
const logger = require("./config/logger");

app.use(pinoHttp({ logger }));

Log inside a handler with req.log

async function list(req, res, next) {
  req.log.info({ path: req.path }, "listing pastes");
  // ...
}

Course example

const pinoHttp = require('pino-http')
const app = express();
app.use(pinoHttp());
app.use(cors());
app.use(express.json());
// Some code omitted for brevity

Additional references & examples

  • pino-http

    HTTP request logging integration for Express.

  • Express.js guide

    Routing, middleware order, and request/response lifecycle.

How to write async route handlers — Intermediate

Mark controller functions async and await service/Knex calls. Always forward failures with next(err) so the error middleware can respond.
Learn: Async handlers must forward rejections with `next(err)`—otherwise Express may not invoke your error middleware. Wrapping async functions or using a small `asyncHandler` utility removes repetitive try/catch blocks.

Supplementary examples

asyncHandler wrapper

function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

router.get("/", asyncHandler(async (req, res) => {
  const rows = await service.list();
  res.json({ data: rows });
}));

Course example

async function create(req, res, next) {
  try {
    const created = await service.create(req.body);
    res.status(201).json({ data: created });
  } catch (err) {
    next(err);
  }
}

Additional references & examples

How to configure logging — Intermediate

Centralize logger setup in a config module with log level and custom request IDs.
Learn: Centralizing logger setup avoids duplicating log level and formatting rules. Use environment variables for LOG_LEVEL in development vs production, and generate a request ID (e.g. nanoid) so log lines for one request can be correlated.

Supplementary examples

Logger module with level from env

const pino = require("pino");
const { nanoid } = require("nanoid");

module.exports = pino({
  level: process.env.LOG_LEVEL || "info",
  genReqId: () => nanoid(8),
});

Course example

const pinoHttp = require("pino-http");
const { nanoid } = require("nanoid");
const level = process.env.LOG_LEVEL
const nodeEnv = process.env.NODE_ENV || 'development'
const prettyPrint = nodeEnv === "development"
const logger = pinoHttp({
genReqId: (request) => request.headers['x-request-id'] || nanoid(),
level,
prettyPrint
});
module.exports = logger;

Additional references & examples

How to enable CORS — Basic — app-wide

Apply the cors middleware on the Express app so all routes allow cross-origin requests.
Learn: Browsers block cross-origin API calls unless the server sends CORS headers. App-wide CORS is simplest when a single frontend origin talks to your API. Tighten options in production—avoid `origin: '*'` if cookies or credentials matter.

Supplementary examples

Default CORS for all routes

const cors = require("cors");
app.use(cors());

Restrict to one frontend origin

app.use(
  cors({
    origin: process.env.FRONTEND_URL || "http://localhost:3000",
    credentials: true,
  })
);

Course example

const logger = require("./config/logger");
const app = express();
app.use(logger);
app.use(cors());
app.use(express.json());
// Some code omitted for brevity

Additional references & examples

How to use environment variables — Basic

Read configuration from process.env (often with dotenv) for ports, secrets, and log levels.
Learn: Twelve-factor apps store config in the environment: ports, database URLs, API keys, and log levels. Never commit secrets. Use `.env` locally with dotenv; in production the host injects variables.

Supplementary examples

Load dotenv at startup

require("dotenv").config();

const PORT = process.env.PORT || 5000;
const DATABASE_URL = process.env.DATABASE_URL;

Fail fast if required env is missing

if (!process.env.DATABASE_URL) {
  throw new Error("DATABASE_URL must be set");
}

Course example

const express = require('express');
const cors = require('cors');
const app = express();
const router = express.Router();
const PORT = process.env.PORT || 5001;
router.get('/', cors(), (req, res) => {
res.json({ message: 'Hello Render!' });
});
app.use('/', router);
app.listen(PORT, () => {
console.log(`Server running on ${PORT} `);
});
module.exports = app;

Additional references & examples

How to return Promises from services — Intermediate

Service functions return promises (or async functions that return data). Controllers await them; do not mix callback-style Knex with async/await in the same path.
Learn: Keep Promise-returning database code in services. The controller awaits one or more service calls and maps results to HTTP. If you return knex(...) directly from a service without await inside, the controller still awaits the same Promise—both styles work if errors propagate to next(err).

Supplementary examples

async service function

async function remove(pasteId) {
  const deleted = await knex("pastes").where({ paste_id: pasteId }).del();
  if (!deleted) {
    const err = new Error("Paste not found");
    err.status = 404;
    throw err;
  }
}

Course example

// service — return the Knex promise (or use async/await inside)
function listByUser(userId) {
  return knex("pastes")
    .where({ user_id: userId })
    .orderBy("created_at", "desc");
}

// controller
async function list(req, res, next) {
  try {
    const rows = await service.listByUser(req.params.userId);
    res.json({ data: rows });
  } catch (err) {
    next(err);
  }
}

Additional references & examples