
API Design in Node.js, v4
Learning Paths:
Table of Contents
Introduction
Introduction
Scott Moss introduces the course and walks through the course website. The API project will be built from scratch and the final code can be found in the GitHub repo linked below.Tooling Overview
Scott walks through the tools and technologies used throughout the course. The application will be a product change log API and it will be built with Node.js, Express, Prisma, PostgreSQL, and hosted on Render.com
API Basics in Node.js
Creating an HTTP Server
Scott creates a basic HTTP server using the built-in http Node module. As requests are made to the server, conditional logic determines how the server should respond.Requests & Responses
Scott examines the request and response objects passed to the createServer callback by the http module. Since the request is being handled by the server, no XHR request will appear in the browser. Server-side JavaScript executes in the Node runtime on the server and not in the browser.Anatomy of an API
Scott explains the different components of an API including HTTP methods, route handlers, and how IP addresses and ports work. Most of the work of an API happens inside the route handler. These handlers can contain logic for determining how to handle requests, authentication, and limit the number of requests to protect the server.Creating a Server with Express
Scott refactors the code to use Express instead of the built-in http module. Express is installed with NPM and provides helper methods for defining routes. Depending on the functionality of the API, route handlers can return anything from basic text or JSON, to HTML files.
Prisma
Object Relational Mapper (ORM)
Scott introduces ORMs, or Object Relational Mappers. ORMs provide an API for conducting database operations and eliminate the need to write SQL statements inside the application. A question about using JSON in Postgres vs. Mongo is also answered in this lesson.Prisma & Render Setup
Scott explains how Prisma, a database-agnostic ORM, will be used in the application. A PostgreSQL database is created on Render.com. Prisma communicates between the application and Render.com using the external database URL located in the Render.com connection settings.Prisma Overview
Scott installs Prisma and runs the CLI tool to generate the required files for the project. The prisma.schema file contains all the schemas for the data models. The database connection string is added to the .env file.Designing a Schema
Scott creates a schema for the User model. Each user has an id, username, password, and a createdAt field. The id field uses a UUID, a unique string that can be generated by Prisma when a user is created.Product Model
Scott creates the schema for the Product model. A relationship between the Product and User models is established using the Prisma @relation attribute.Update & UpdatePoint Models
Scott finishes the last two models, Update and UpdatePoint. Relationships are established between the two models. A question about how Prisma supports multiple database platforms is also answered in this lesson.Migrations
Scott runs a migration that syncs the database structure with the Prisma schema. A migrations directory is also created which stores every executed migration. The benefit of having migration files is the ability to track them with source control and ensure every developer is aware of changes made to the database.
Routes & Middleware
Defining Routes
Scott explains the CRUD operations Create, Read, Update, and Delete. The routes required to execute these operations are created for the Product, Update, and UpdatePoint models. A question about GRPC vs REST is also answered in this lesson.Importing the Application Router
Scott mounts the router in the main application and configures it to be used under the "/api" parent route. This architecture makes the application more modular because the business logic for different areas of the application can be defined in separate routers.Testing the API with Thunder Client
Scott demonstrates using the VSCode extension Thunder Client to test API endpoints. An endpoint needs to return something. Otherwise, the request will hang. With HTTP, the connection with the server is closed once something is returned.Middleware
Scott introduces the concept of middleware and installs Morgan, a logging middleware for Express. Middleware is a function that can be called for any request or only specific routes. The middleware function calls a next() method when it's complete so the request can finish being processed by the server.Creating a Custom Middleware
Scott creates a custom middleware that adds a property to the request object. This property is now available to any subsequent middleware and the route handler.
Authentication
Creating a JWT
Scott introduces JSON web tokens or JWTs. The purpose of a JWT is to help identify an authenticated user on the server. The JWT is created by combining the user's ID and username with a stored secret. This creates a deterministic string to identify the user.Protecting Routes
Scott begins implementing a middleware to protect the API routes. The middleware first checks to see if a bearer token is present. If not, it returns and 401 not authorized error.Validating a Bearer Token
Scott adds code to validate the JWT token. The validation logic ensures the token is well-formed. If not, an error is thrown, and the server again issues a 401 unauthorized request. A question about API rate limiting is also addressed in this lesson.Authorization Headers
Scott reviews how authorization headers enforce authentication rules on a server.Comparing & Hashing Passwords
Scott creates functions for comparing and hashing passwords. The hash function takes the user-submitted password, combines it with a salt, and returns a hashed password. The compare function will look at a plaintext password and determine if it matches a hashed version.Creating Users
Scott implements the createNewUser function, which hashes the password and creates and new user record in the database for that username and hash combination. The function also creates a JWT and returns the token.Authenticating a User
Scott implements the signin function which will search the database for a user with the submitted username and compare the submitted password with the hashed password for that user.Adding User Routes
Scott adds the routes for creating a user and signing in a user. Both routes result in a JWT being returned if the request is completed successfully. The JWT can then be passed as a bearer token to the protected API routes.
Route & Error Handlers
Validation Overview
Scott explains why validating user input is important. There are many third-party libraries specializing in input validation. The express-validator module will be used in this application.Adding Validation to Routes
Scott adds the express-validator module to the API and uses it as middleware to check the existence of the name property on the body during a product update.Route Validation Exercise & Solution
Students are instructed to add validation to the remaining product, update, and updatepoint PUT/POST routes.Get Product Handlers
Scott begins creating route handlers for the products table. One route handler returns all the user's products. The other returns a single product based on the ID passed to the route.Create & Update Product Handlers
Scott codes the create and update product handlers. Both handlers will not only send a product name to Prisma but also the current user's ID to ensure the product that's created/updated belongs to the user.Applying Product Route Handlers
Scott adds the product route handlers to the router and tests the application with Thunder Client.Update Handlers Exercise & Solution
Students are instructed to create route handlers for the Update and UpdatePoint routes. This lesson includes the solution to the getUpdate and getOneUpdate handlers.Create & Delete Handlers Solution
Scott continues the solution for the Update Handlers exercise. This lesson includes the solution to the createUpdate, updateUpdate, and DeleteUpdate route handlers.Debugging Routes
Scott uses Thunder Client to test and debug all the route handlers in the application. Once a user is authenticated, a product is created along with updates for the product.Creating Error Handlers
Scott demonstrates how errors cause a Node server to shut down. Express has built-in error handling, however, the default behavior is to output the error message and stack trace. Creating error-handling middleware allows the application to respond differently depending on the error thrown.Async Error Handlers
Scott uses the next function to handle an async error. Calling the next function with the error as a parameter sends the error to the next middleware and allows it to be handled like a synchronous error.Error Handlers Exercise & Solution
Students are instructed to add error handling to all the API route handlers in the application.Handling Errors with process.on()
Scott explains how errors can be handled outside of Express using the process.on API. Similar to using addEventListener, the "on" method has named events like uncaughtException and unhandledRejection which will trigger when certain errors are thrown.
Config, Performance, & Testing
Environment Variables
Scott introduces environment variables. The NODE_ENV variable lets the application and any other package know the environment. This could be development, testing, production, or any other string. Using custom environment variables for database URIs or secrets allows confidential information to be injected into the application based on the environment.Creating Environment Configurations
Scott creates configuration files for each environment and uses the lodash.merge module to merge custom environment variables into a single configuration object. This architecture allows a default configuration to be created, and for each environment, a customization for a specific environment can override the default.Performance Management with Async
Scott explains the difference between blocking and nonblocking code. Whenever possible, developers should use asynchronous APIs because they are nonblocking and more performant for the server when executing multiple requests.Unit Testing Overview
Scott describes unit tests as testing individual pieces of logic independently. In order for unit tests to be effective, the code should be written in a testable way. For example, function arguments are easier to test than closure variables.Unit Testing with Jest
Scott creates a __tests__ directory and writes a unit test. The describe block is a wrapper around one or more tests. The "it" method contains a statement about what outcome is expected for the individual test.Integration Testing with SuperTest
Scott demonstrates how integration tests can be used to test an entire route's functionality. The supertest module provides methods for building requests and validating the result of those requests.Testing the Create User Route
Scott uses the supertest module to write an integration test for the createNewUser route handler. A mock object is sent along with the request with the username and password. The importance of using a testing database is also discussed in this lesson.
Deployment
Deployment Setup
Scott prepares the application for deployment. A build script is created to build the TypeScript files into JavaScript files.Deploying to Render
Scott deploys the application to Render.com. When the code is committed to GitHub, Render.com will pull in the latest version of the code and redeploy it. Environment variables are also configured in the Render.com web service wizard.