When using express with Typescript sometimes we want to be able to know the type of the request body or params, sometimes we even need to know the types needed in the response body, let's think about another case where for certain requests we expect to receive a custom security token in the header, how can we define this types in Typescript?
First we need to install the typescript types of express, we can do this with the following command
npm i --save-dev typescript ts-node @types/express
Now we can extend or use the types included on express, first we are going to use them directly, and then we will create our own interfaces extending this types, lets look at the following basic express app
import express, { Application, Request, Response } from "express";
const port = 3015;
// New express application instance
const app: Application = express();
// Allow json in body params
app.use(express.json({ limit: "250mb" }))
/**
* Route using body params, types for email and password and for response
*/
app.post("/body-params", [], (req: Request<any, any, { email: string, password: string }, any, any>, res: Response<{ success: boolean, data: { email: string }}>) => {
// Get variables from request body, with types
const { email, password } = req.body
// Because types if we return something that is not expected we get a typing error
res.status(200).send({ success: true, data: { email } })
})
/**
* Route using query params and query string with types and expects a string array on response body
*/
app.get("/query-params/:userId/:bookId", [], (req: Request<{ userId: number, bookId: number }, any, any, { bookName: string }, any>, res: Response<string[]>) => {
/**
* Get variables from query params
*/
const { userId, bookId } = req.params
/**
* Get variables from request query
*/
const { bookName } = req.query
// Response expect an array of strings
res.status(200).send(["Book 1", "Book 2"])
})
/**
* Begin listening on port selected
*/
app.listen(port, () => {
console.log(`Server is running on port ${port}...`);
});
When using express types we need to use the following generics
Request
P = path params, expects a [key: string]: string dictionary
T = response body, expects any
R = request body, expects any
S ReqQuery = request query, expects any
L Locas = request locals, expects any
Request<P, T, R, S, L>
examples
// Request with any in all Generics
Request<any, any, any ,any ,any>
// Request expecting variables in body and request query
Request<any, any, { userId: number } ,{ bookId: number } ,any>
// Request expecting variables in query params
Request<{ userName: string }, any, any ,any ,any>
Response
S ReqQuery = response body, expects any
L Locas = response locals, expects [string]: any
Response<S, L>
examples
// Response expects string array in response body
Response<string[]>
// Response expects success boolean array and number in data
Response<{ success: boolean, data: number }>
With the following information now we could create our own interfaces extending express types
import { Request, Response } from "express";
import { IncomingHttpHeaders } from 'http';
export interface HeadersWithToken extends IncomingHttpHeaders{
token?: string
}
/**
* Request that expects a token variable on headers
*/
export interface AuthenticatedRequest<
P extends { [key: string]: string } = {
[key: string]: string;
},
T = any,
R = any,
S = any,
> extends Request<P, T, R, S> {
headers: HeadersWithToken,
}
/**
* Response with types for response body, we expect a generic,
* so we can put any data we want on the data of the response
* we can have multiple responses
*/
export interface FormattedResponse<ResponseData> extends Response<{
success: boolean, data: ResponseData
}>{}
Now we can use our own interfaces
app.get("/test-route", [], (
req: AuthenticatedRequest,
res: FormattedResponse<string>
) => {
const { token } = req.headers
if(token){
return res.status(200).send({success: true, data: token})
}
return res.status(200).send({success: false, data: "Token Missing"})
})