Netflix
Course Description
Build scalable APIs for any application with Node.js and Express. Learn RESTful API design best practices as you design routes, run Postgres migrations, and secure your API with JWT-based authentication. Bullet-proof your API with TypeScript, runtime schema validation with Zod, and comprehensive integration testing strategies. Create a production deployment and ship your next API today!
This course and others like it are available as part of our Frontend Masters video subscription.
Preview
CloseCourse Details
Published: September 29, 2025
Learning Paths
Learn Straight from the Experts Who Shape the Modern Web
Your Path to Senior Developer and Beyond
- 250+ In-depth courses
- 18 Learning Paths
- Industry Leading Experts
- Live Interactive Workshops
Table of Contents
Introduction
Section Duration: 24 minutes
- Scott Moss introduces the course by sharing a bit about his background and teaching style. He outlines the course goals, including building a habit-tracking API, using Node.js, Express, TypeScript, and Postgres, and covering topics like RESTful design, authentication, testing, and deployment.
- Scott outlines the API project, which will cover controllers, databases, middleware, routes, Postman testing, integration tests, seeding data, and JSON APIs.
- Scott explains the differences between Node.js and browser JavaScript, noting the browser’s focus on DOM and HTTP calls versus Node.js’s server-side tools like file access and server building. He also covers package.json for dependencies and tsconfig.json for TypeScript setup.
Getting Started with Express
Section Duration: 57 minutes
- Scott explains how to create a server with Express in a Node.js project, stressing proper source setup and dependency installation. He demonstrates setting up routes, handling requests, running the server on a port, and using logging to monitor activity.
- Scott demonstrates testing APIs with Postman including, making a GET request to a local server and explains how servers can return different parseable data types like JSON or HTML.
- Scott discusses using typed environment variables in Node.js to securely manage API keys and enable dynamic configuration across local, staging, and production environments.
- Scott covers managing many environment variables by using a typed TypeScript file (ENV.ts) with Zod for validation, ensuring autocomplete, required variables, and clear distinctions between production, development, and testing environments.
- Scott demonstrates using Zod to create schemas that validate environment variables like Node ENV, port, database URL, and JWT secret, emphasizing proper validation to prevent runtime errors.
- Scott recommends setting up environment variable files to ensure required variables and a predictable environment, advises against committing them to GitHub, and suggests advanced practices like secret rotation and using tools like HashiCorp Vault.
Creating Express Routes
Section Duration: 1 hour, 1 minute
- Scott explains common HTTP methods like GET, POST, PUT, PATCH, and DELETE, their purposes in APIs, and the effect of each request. He also touches on REST principles, CRUD routes, and the flexibility of REST compared to GraphQL.
- Scott explains that requests to unhandled routes cause 404 errors, while not responding can hang the server. He emphasizes understanding HTTP as a client-server system and the constraints affecting real-time communication and dynamic Express paths.
- Scott demonstrates how to create sub-routers in Express for resources like users, habits, and authentication, setting up CRUD routes and handling habit completions. He also covers HTTP status codes, emphasizing proper use of success and error responses.
- Scott reviews creating user routes in Express, stressing practice and rewriting code. He covers creating, updating, and deleting users, admin and authentication considerations, and additional CRUD operations for completeness.
- Scott explains mounting routers in Express using .use for routes like /api/auth, /api/users, and /api/habits, highlighting the decoupling benefits. He also notes that Express handles responses and errors without enforcing REST-specific actions for HTTP verbs.
- Scott demonstrates creating nested routers in Express for API segmentation, highlighting route collision prevention with enums or globals. He also covers conditional routes, feature flags, API versioning, and organizing routes by resource or feature for cleaner structure.
Middleware
Section Duration: 1 hour, 7 minutes
- Scott explains middleware as code between other code, handling tasks like error interception and modifying requests. He stresses managing responses carefully, since sending one closes the connection and mismanaged middleware can cause issues.
- Scott covers middleware across the stack, from network-level functions like Next.js edge functions to front-end interceptors. He also highlights Express middleware flow using the next argument and its use for logging, analytics, and global operations.
- Scott discusses when to call next(), such as for checks or authorization, and returning it properly to avoid hanging requests. Scott also discusses how passing an argument to next() signals an error for Express to handle.
- Scott introduces global middleware including, CORS for browser security, Helmet for best practices, and Morgan for logging, demonstrating their implementation in Express and stressing proper CORS configuration and JSON parsing.
- Scott demonstrates using Zod middleware to validate request bodies, handle errors, and enforce input checks before route handlers, improving code clarity and preventing unexpected data issues.
- Scott explains validating URL parameters and queries in Express, noting they are always strings. He also suggests using UUIDs over incremental IDs and differentiates local, global, and route-level middleware.
- Scott discusses middleware factories for async error handling in Express, stressing correct middleware order, avoiding modifying responses after headers, and efficiently handling background tasks to prevent race conditions.
Postgres Databases Setup
Section Duration: 1 hour, 54 minutes
- Scott explains database schema design, focusing on simplicity, relational concepts, and modeling data around ownership and user journeys. He highlights primary and foreign keys, relationships, and a purpose-driven approach to data modeling.
- Scott discusses Postgres as a versatile open-source database with features like JSON and geospatial search. He emphasizes using Node.js with Postgres for its ecosystem and ORM support, explaining ORMs provide type safety and help prevent SQL injection.
- Scott explains database migrations for maintaining consistency and avoiding application-breaking changes. He covers schema and data migrations, prefers non-destructive changes, and highlights using Drizzle over Prisma for TypeScript flexibility.
- Scott demonstrates creating tables using Drizzle ORM, defining columns, primary and foreign keys, data types, constraints, indexes, and relationships. He shows examples for users, habits, entries, tags, and a many-to-many table.
- Scott walks through setting up one-to-many and many-to-many relationships between tables, specifying linking fields, and avoiding circular dependencies. He also suggests splitting schemas into separate files for larger projects.
- Scott demonstrates exporting TypeScript types and Zod schemas for tables like user, habit, entry, and tag. He shows validating inputs against database rules to ensure data consistency at runtime.
- Scott discusses best practices for database schemas, including adding timestamps, enforcing constraints, and using descriptive names. He also recommends avoiding destructive changes in database migrations by following an additive approach.
- Scott walks through setting up a Postgres database with Neon, configuring the database URL in .env, and creating a connection with pooling. He discusses avoiding memory leaks and using service databases in development.
- Scott explains adding scripts in package.json to manage the database outside the ORM, including syncing, migrating, seeding, and using Drizzle Studio. He demonstrates syncing the schema and visualizing the database.
- Scott discusses using seed scripts to populate databases for testing, development, or reproducing issues. He also demonstrates creating flexible seed scripts while ensuring data integrity and avoiding production runs.
- Scott explains how Drizzle Studio works with Drizzle Kit, managing large seed data in separate files for testing. He also suggests being careful schema changes in production and generating single migration files for multiple changes.
Authentication & Authorization
Section Duration: 1 hour, 43 minutes
- Scott discusses identification, authentication, and authorization in API security. He covers methods like sessions, JWTs, and API keys, their use cases, common flows, and best practices for secure and scalable APIs.
- Scott demonstrates the sign-up process for users, including validating input, checking uniqueness, hashing passwords for security, saving user data in the database, and providing a JSON web token for API access.
- Scott walks through how to create Express controllers for sign-up requests, emphasizing error handling, pre-validation, password hashing, and using JWTs for authentication.
- Scott explains generating JWTs with identifying traits like user ID, email, and username. He covers secure token creation, expiration, and TypeScript type-checking for consistent request bodies.
- Scott demonstrates using Zod middleware for runtime input validation beyond database checks. He also touches on refresh tokens for handling expired JWTs and emphasizes practical problem-solving skills.
- Scott explains the process of signing users in by comparing hashed passwords in the database to the password provided during sign-in. Scott also discusses the significance of salt in preventing identical hashed passwords for users with the same password, ensuring security.
- Scott demonstrates implementing login functionality with a sign-in controller. He explains how to query the database for a user based on the provided email, compare passwords securely using bcrypt, generate a token upon successful login, and handle errors effectively.
- Scott discusses securing APIs using JSON Web Tokens (JWT) by attaching it to the authorization header of each request, the server can verify, decode, and authenticate the token to ensure secure communication.
- Scott demonstrates creating and authentication middleware that checks for a bearer token, verifies it, and attaches the user to the request object, using TypeScript for typing.
- Scott discusses securing routes like user and habit routes with middleware that authenticates tokens and ensures only authorized users can access them. He also discusses organizing protected routes under separate routers.
- Scott discusses applying authentication middleware at the router level for consistent security, centralizing logic, and maintaining endpoints. He also covers best practices like avoiding logging tokens, using HTTPS, token expiration, refresh tokens, and request validation.
CRUD Routes
Section Duration: 46 minutes
- Scott reviews RESTful route design, explaining how CRUD controllers map to resources like habits and users. He discusses how requests flow through middleware for authentication and validation before reaching controllers, and he reviews common HTTP status codes such as 200 for success, 401 for authentication errors, 404 for missing resources, and 500 for server errors.
- Scott explains how to create a habit controller by working with database tables, managing relations like habit tags, and using transactions to maintain integrity. He also shows how to create a habit, handle errors, validate inputs with schemas, and connect the controller to the habit route.
- Scott creates a getUserHabits function to retrieve all habits for a user, joining tables and transforming data in JavaScript to include tags. He also notes that transformations are usually done in the database for efficiency, and that GET requests do not require input validation.
- Scott explains how to update a habit by separating tag IDs from the request body, running a transaction to ensure integrity, and checking authorization before saving changes. He also covers managing tag associations, returning proper responses, and handling errors during the process.
- Scott demonstrates using Postman to test creating, retrieving, and updating habits. He also shows how to structure JSON requests.
Error Handling & Testing
Section Duration: 1 hour, 23 minutes
- Scott explains how to centralize error handling in Express with middleware. Error handlers take four arguments, with the first one always being an error. Once an error is triggered and next() is called, all remaining middleware is skipped, and the request goes directly to the nearest error handler.
- Scott demonstrates creating and registering an error handler in Express. He shows how to use custom error classes, logging, or services like Sentry, and implements a global handler for flexible error management.
- Scott discusses setting up a separate test database to keep development data clean. He recommends Vitest, configures the test environment to avoid conflicts, and stresses the need for stateless tests for consistent test results.
- Scott sets up a global test configuration by organizing folders, importing modules, syncing schemas, and running raw SQL. He also explains how these steps keep tests isolated and maintain a clean database state.
- Scott demonstrates dropping and recreating tables in tests to ensure a consistent starting state and catch schema-related bugs early. He also creates helper functions for tasks like adding test users or habits and cleaning up data efficiently.
- Scott explains how to set environment variables in package.json with cross-env to run tests in the correct environment. He also discusses using hooks like beforeEach and afterEach and writes a simple test to confirm the setup.
- Scott discusses testing production APIs to ensure reliability. He reviews unit, integration, end-to-end, and snapshot tests, noting integration tests give the most confidence. He also covers best practices such as mocking, isolation, and testing edge cases.
- Scott demonstrates testing user registration with supertest, using describe, it, and assertions to validate different scenarios and expected responses.
- Scott walks through how to test login endpoints, emphasizing checks for successful authentication and proper error handling.
Production Deployment & CI/CD
Section Duration: 24 minutes
- Scott walks through setting up the course project for deployment to Render, creating a production database and running an initial migration to set up the schema. He generates a migration locally, applies it to production, and warns against pushing schemas directly.
- Scott configures the port dynamically with process.env.port, prepares production environment variables, creates secure values, avoids hard-coded localhost, and sets the Node.js version in package.json.
- Scott walks through deploying to the course project to Render by creating a web service, setting environment variables, deploying, and testing the app.
Wrapping Up
Section Duration: 14 minutes
- Scott wraps up the course by suggesting further learning with tools like GraphQL, gRPC, serverless APIs, Web Sockets, Docker, and GitHub Actions. He recommends students continue experimenting, expanding on the API, and using it as a reference.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops