API Design in Node.js v3
Introduction
API
What is an API
- tldr; a server that creates an HTTP interface for interacting with some data
- Applicaction programming interface
- The name is used EVERYTHERE
- Usually a server on some remote machine that dictates how another application can interact with some data
- Basic data operations like, Create, Read, Update, Destory(CRUD)
What about REST?
- tldr; most popular API design pattern, but is not the silver bullet. Very blurry
- An API design that combines DB resources, route paths, and HTTP verbs to allow application describe what action they are trying to perform
- Popularized when SaaS products starting offering APIs for integrations
- Works with basic data models
- Hard to scale with complex data models and client requirements
Node.js for APIs
- Node.js and APIs
- tldr; build for high concurrent APIs that are not CPU intensive
- Node.js is JavaScript, it’s async and event driven
- Single threaded(can optimize)
- When kept async, Node can handle a high amount of concurrent request
- Not great for CPU intensive work(data crunching, ML, big maths)
- So many open source tools to help build APIs
Express
- Express
- tldr; the standard API framework for Node.js
- Handles all the tedious tasks like managing sockets, route matching, error handling, and more
- Open source
- Has a huge community and support from anything that has to do with APIs in Node.js
- Not going anywhere anytime soon
MongoDB
- MongoDB
- tldr; the go-to non-relational DB, works like a dream in Node.js
- Non-relational document store that is easy to get started and scales well
- Open source and backed by a big company
- Tons of hosting solutions
- ORM/ODM and other libs are some of the best for many DB
Express
Setup Code & Express
Routing & Middleware
- What is Middleware
- tldr; list of functions that execute, in order, before your controllers
- Allow you to execute functions on an incoming request with guaranteed order
- Great for anthentication, transforming the request, tracking, error handling
- Middleware can also respond to request like a controller would, but that is not their intent
Custom Middleware
1 | const log = (req, res, next) => { |
REST Routes with Express
- REST routes with Express
- tldr; Express was designed with REST in mind and has all you need
- Express has a robust route matching system that allows for exact, regex, glob, and parameter matching
- It also supports HTTP verbs on a route based level. Together with the routing, you can create REST APIs
- app.get()
- app.post()
- app.put()
- app.delete()
Route Order
- REST routes with Express
- Routes match in the order that they were defined(top to bottom)
1 | app.get('/data', (req, res) => { |
- 客户端的收到的请求体中message为’hello’,所以同样的路由只会匹配第一个。
1 | app.get('/data', (req, res, next) => { |
- 更改为如上代码后,message为’world‘,但不建议这么做,应该使用中间件实现类似需求,以增加可读性。
Router & Sub Routes
- REST routes with Express
- For abstraction, Express allows you to create sub routers that combine to make a full router
1 | const router = express.Router() |
Router Verb Methods
1 | // 同一请求路径可以根据不同动词进行处理 |
Exercise: Router & Sub Routes
1 | // server.js |
1 | // item.router.js |
Data Modeling
Data Modeling with MongoDB
- Schemas for a schemaless DB?
- tldr; You should always use a Schema for models, and mongoose makes it easy
- MongoDB is a Schemaless document store, but you should always use schemas if you don’t want to go crazy
- MongoDB has added support for creating schemas, but Mongoose is much better
- We can create models for each REST resource we want to expose via the API
Transitioning from Schemas to Models
- Schemas to models
- tldr; Schemas are the instructions for the model
- Schemas hold the instructions for models. Things like validations, names, indexes, and hooks
Exercise: Mongoose Schema
solution
1 | import mongoose from 'mongoose' |
Controllers & Models
Controllers & Models Overview
- Routes and Controllers
- tldr; controllers are just middleware but with the intent on returning some data
- Controllers handle what a Route + Verb combo can access from the DB
- Think of them as the final middleware in the stack for a request. Their is no intent to proceed to another middleware function after a controller
- Controllers implement the logic that interacts with our DB models
- Can generalize controllers to work for many models because we’re going with a REST approach which requires CRUD actions on resources
Express Response Object
- res对象可以链式调用
Refactoring CRUD Routes with Models
Creating, Read, Update & Delete a Document
- Using models
- tldr; Mongoose models work very nicely with CRUD
- C -
model.create()
,new model()
- R -
model.find()
,model.findOne()
,model.findById()
- U -
model.update()
,model.findByIdAndUpdate()
,model.findOneAndUpdate()
- D -
model.remove()
,model.findByIdAndUpdate()
,model.findOndeAndRemove()
CRUD Controller Design Overview
- GET / Read many
- GET /:id Read one
- POST / Create one
- PUT /:id Update one
- DELETE /:id Delete one
Exercise: CRUD Controller
crud.js
1 | export const getOne = model => async (req, res) => { |
Auth
Authentication in APIs Overview
Authentication with JWT(JSON Web Token)s
Auth basics
- tldr; You can never truly protect an API, but requiring authentication makes it a bit safer
- Authentication is controlling if an incoming request can proceed or not
- Authorization is controlling if an authenticated request has the correct permissions to access a resource
- Identification is determining who the requester is
JSON Web Token Authentication
- JWT authentication
- tldr; tokens passed every request to check auth on the server
- A bearer token strategy that allows the API to be stateless with user auth
- Created by a combination of secrets on the API and a payload like a user object
- Must be sent with every request where the API will then try to verify the token was created with the expected secrets
- After sucessful verification, JWT payload is accessible to the server. Can be used to authorization and identification
Exercise: Secure An API with JWTs
auth.js
1 | import config from '../config' |
server.js
1 | app.post('/signup', signup) |