0.3.0 • Published 2 years ago

express-typescript-decorators v0.3.0

Weekly downloads
-
License
ISC
Repository
github
Last release
2 years ago

Express TypeScript Decorators

Create Express APIs using TypeScript decorators that naturally generate OpenAPI documentation to describe your API.

codecov

Working Example

See a working example of express-typescript-decorators User Management

Overview

  • Create classes whose methods represent Express routes
  • Decorate your class to add additional Express functionality i.e. @Controller(), @HttpGet, @Middleware etc.
  • Further Describe your API with a title and description in OpenAPI.json
  • Automatically generate OpenAPI JSON documentation
  • Link your API documentation to Swagger for rich API documentation tools

Typedoc Documentation

Example Application

Consider the following three examples to see how any Express application can become a fully documented API.

Example Express Application before using Express TypeScript Decorators

Here is an example of an Express Application without TypeScript.

controllers/user.js

const express = require('express');
const UserService = require('../services/user');

const router = express.Router();

router.post('/user', async (req, res) => {
    const username = req.body.username;
    const password = req.body.password;

    try {
        const user = await UserService.create(username, password);
        res.status(201).send(user);
    } catch(e) {
        res.send(400);
    }
});

router.get('/user', async (req, res) => {
    const users = await UserService.list();
    res.send(users);
});

module.exports = router;

index.js

const express = require('express');
const bodyParser = require('body-parser');
const UserController = require('./src/controllers/user');
const app = express();

app.use(bodyParser.json());

app.use('/api', UserController);
app.listen(3000, () => console.log('Express Application Started!'));

Converting to TypeScript and Decorators

Here's how this same Express Application would be implemented using Decorators and TypeScript

controllers/user.ts

import UserService from '../services/user';
import {
    Controller,
    HttpPost,
    HttpGet,
    Response,
    ExpressResponse
} from 'express-typescript-decorators';

@Controller()
class UserController {

    @HttpPost('/user')
    async createUser(
        @RequestParam('username') username: string,
        @RequestParam('password') password: string,
        @Response() res: ExpressResponse
    ): Promise<void> {
        try {
            const user = await UserService.create(username, password);
            res.status(201).send(user);
        } catch(e) {
            res.send(400);
        }
    }

    @HttpGet('/user')
    async listUsers(
        @Response() res: ExpressResponse
    ): Promise<void> {
        const users = await UserService.list();
        res.send(users);
    }
}

export default router;

index.ts

import express from 'express';
import bodyParser from 'body-parser';
import {useController} from 'express-typescript-decorators';
import UserController from './src/controllers/user';

const app = express();

app.use(bodyParser.json());
app.use('/api', useController(UserController));
app.listen(3000, () => console.log('Express Application Started!'));

Full Swagger API Documentation Example

Taking this example Express Application a step further, we can produce more comprehensive API documentation in our code with the addition of @HttpReponse, @RequestBody and providing descriptions in calls to @RequestParam. This content naturally flows into Swagger UI.

controllers/user.ts

import UserService from '../services/user';
import {
    Controller,
    HttpPost,
    HttpGet,
    HttpResponse,
    RequestBody,
    Response,
    ExpressResponse
} from 'express-typescript-decorators';

@Controller()
class UserController {

    @RequestBody('application/json', 'POST Body that contains all information required to create a user', true)
    @HttpResponse(201, 'User Created Successfully', 'application/json', {
      id: 123,
      firstName: "Paul",
      lastName: "Forbes",
      email: "paulforbes42@gmail.com"
    })
    @HttpResponse(400, 'Failed to create user')
    @HttpPost('/user', 'Create a new user for the system')
    async createUser(
        @RequestParam('username', 'Username for the new user record', 'exampleUsername') username: string,
        @RequestParam('password', 'Password for the new user record', 'Pa33w0rd') password: string,
        @Response() res: ExpressResponse
    ): Promise<void> {
        try {
            const user = await UserService.create(username, password);
            res.status(201).send(user);
        } catch(e) {
            res.send(400);
        }
    }

    @HttpGet('/user', 'Generate a list of users in the system')
    async listUsers(
        @Response() res: ExpressResponse
    ): Promise<void> {
        const users = await UserService.list();
        res.send(users);
    }
}

export default router;

index.ts

import express from 'express';
import bodyParser from 'body-parser';
import {
    getOpenAPIJson,
    setOpenAPIInfo,
    useController,
} from 'express-typescript-decorators';
import UserController from './src/controllers/user';

const app = express();

setOpenAPIInfo({
    title: "Example User API",
    summary: "A sample Express Application to create and list users",
    description: "This sample Express Application demonstrates usage of decorators to create Express Routers called 'controllers' from Classes.  Methods of the class can represent the routes of the Express Application when correctly decorated.  Optionally, OpenAPI documentation can be generated to describe the API.",
    contact: {
        name: "Paul Forbes",
        email: "paulforbes42@gmail.com"
    },
    license: {
        name: "Apache 2.0",
        identifier: 'Apache-2.0',
        url: "https://www.apache.org/licenses/LICENSE-2.0.html"
    },
    version: "0.0.1"
});

app.use(bodyParser.json());
app.use('/api', useController(UserController));
app.use('/api-docs', getOpenApiJson());
app.listen(3000, () => console.log('Express Application Started!'));

Navigating to http://localhost:3000/api-docs will provide a JSON representation of the OpenAPI documentation you've created directly from the code for your API.

TypeScript configuration tsconfig.json

The following configurations will need to be enabled in tsconfig.json for your project order for the decorators to appropriately capture metadata from you controllers and routes.

tsconfig.json

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

Decorators and Utility Methods

The ulitmate goal of this package is to allow a clean and consistent interface to provide rich OpenAPI documentation around your API while, alternatively, supporting a simple approach to define class based Express routers in TypeScript. Observe the following Decorators and utility methods at your disposal. The more optional information you provide about each route, the more content which will be available in your OpenAPI JSON file which can be consumed by Swagger UI for an always up-to-date API documentation site.

Decorators

Controller

@Controller<T>(path?, tag?, description?)

Class decorator used for classes that represent a grouping of Express routes

Example

@Controller('/api', 'User', 'User Management Routes')
class User {
 ...
}

Parameters

NameTypeDescription
path?stringURL path to prefix all Express routes within the class
tag?stringOpenAPI grouping for all Express routes within the class
description?stringOpenAPI description that will be associated with the tag for this class

HttpDelete

@HttpDelete<T>(path, description?)

Method decorator that indicates the method should be accessible through an HTTP Delete operation

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpDelete('/user', 'Mark a user as deleted')
 async deleteUser(
   @RequestParam('userId') userId: number, 
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
pathstringURL which this method should be access through as a HTTP Delete operation
description?stringDescription of this route to be included in the OpenAPI documentation

HttpGet

@HttpGet<T>(path, description?)

Method decorator that indicates the method should be accessible through an HTTP Get operation

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpGet('/user', 'List users in the system')
 async listUsers(
   @Query('search') search: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
pathstringURL which this method should be access through as a HTTP Get operation
description?stringDescription of this route to be included in the OpenAPI documentation

HttpPost

@HttpPost<T>(path, description?)

Method decorator that indicates the method should be accessible through an HTTP Post operation

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpPost('/user', 'Create a new user')
 async createUser(
   @RequestParam('username') username: string,
   @RequestParam('password') password: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
pathstringURL which this method should be access through as a HTTP Post operation
description?stringDescription of this route to be included in the OpenAPI documentation

HttpPut

@HttpPut<T>(path, description?)

Method decorator that indicates the method should be accessible through an HTTP Put operation

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpPut('/user/:userId', 'Update an existing user')
 async createUser(
   @UrlParam('userId') userId: number,
   @RequestParam('password') password: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
pathstringURL which this method should be access through as a HTTP Put operation
description?stringDescription of this route to be included in the OpenAPI documentation

HttpResponse

@HttpResponse<T>(statusCode, description)

Method decorator which describes an HTTP status code that can be returned by this Express route. This decorator can be applied multiple times to a single Express route to describe all status codes produced by this route.

Example

@Controller('/api', 'User', 'User Management Routes')
class User {
 @HttpResponse(201, 'New user record created', 'application/json', {
   id: 123,
   firstName: "Paul",
   lastName: "Forbes",
   email: "paulforbes42@gmail.com"
 })
 @HttpResponse(400, 'Missing required data')
 @HttpPost('/user', 'Create a new user')
 async createUser(
   @RequestParam('username') username: string,
   @RequestParam('password') password: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
statusCodenumberHTTP status code
descriptionstringDescription of the scenario in which this HTTP status code will be produced

Middleware

@Middleware<T>(middleware, security?)

Method decorator which accepts an array of Express middleware callback functions to run prior to the Express route being executed

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @Middleware([verifyAuthenticated, checkAdminPermissions], [{"bearerAuth":[]}])
 @HttpPost('/user', 'Create a new user')
 async createUser(
   @RequestParam('username') username: string,
   @RequestParam('password') password: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
middlewareMiddlewareFunction[]Array of Express middleware callback functions
security?OpenAPISecurityRequirement[]Array of security requirement objects. Must match one item in the securitySchemes in OpenAPI.json

Query

@Query<T>(key, summary?, exampleValue?, required?, deprecated?)

Parameter decorator which defines a query string parameter to be provided to the Express route

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpGet('/user', 'List users in the system')
 async listUsers(
   @Query('search', 'Search string to lookup users by first or last name', 'Paul', false, false) search: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
keystringname of parameter in the query string
summary?stringdescription of the parameter for OpenAPI documentation
exampleValue?string | number | booleanexample of expected data
required?booleanindicate if this parameter is required
deprecated?booleanindicate if this parameter is deprecated

Request

@Request<T>()

Parameter decorator which injects the Express Request object into the decorated Express route

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpGet('/user', 'List users in the system')
 async listUsers(
   @Query('search') search: string,
   @Request() req: ExpressRequest,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}
Parameters
NameType
targetT
propertyKeystring
parameterIndexnumber

RequestBody

@RequestBody<T>(contentType, description?, required?)

Method decorator to describe the expected request payload

Example

 * @Controller('/api', 'User', 'User Management Routes')
class User {

 @RequestBody('application/json', 'User information for new user record', true) 
 @HttpPost('/user', 'Create a new user')
 async createUser(
   @RequestParam('username') username: string,
   @RequestParam('password') password: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
contentTypestringcontent type of payload i.e. "application/json"
description?stringdescription of the payload for OpenAPI documentation
required?booleanindicate if the payload is required

RequestParam

@RequestParam<T>(key, summary?, exampleValue?, required?)

Parameter decorator which defines a request body parameter to be provided to the Express route

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpPut('/user/:userId', 'Update an existing user')
 async createUser(
   @UrlParam('userId') userId: number,
   @RequestParam('password') password: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
keystringname of parameter in request body
summary?stringdescription of the parameter for OpenAPI documentation
exampleValue?string | number | booleanexample of expected data
required?booleanindicate if this parameter is required

Response

@Response<T>()

Parameter decorator which injects the Express Response object into the decorated route

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpGet('/user', 'List users in the system')
 async listUsers(
   @Query('search') search: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

UrlParam

@UrlParam<T>(key, summary?, exampleValue?)

Parameter decorator which defines a url parameter to be provided to the Express route

Example

@Controller('/api', 'User', 'User Management Routes')
class User {

 @HttpPut('/user/:userId', 'Update an existing user')
 async createUser(
   @UrlParam('userId') userId: number,
   @RequestParam('password') password: string,
   @Response() res: ExpressResponse
 ): Promise<void> { 
   ... 
 }
}

Parameters

NameTypeDescription
keystringname of parameter in request body
summary?stringdescription of the parameter for OpenAPI documentation
exampleValue?string | number | booleanexample of expected data

Functions

getOpenAPIJson

getOpenAPIJson(): (req: Request, res: Response) => void

Return an Express route which will render the OpenAPI documentation for the API in JSON format.

Example

app.use('/api-docs', getOpenAPIJson());

Returns

fn

Get API method

▸ (req, res): void

Parameters
NameType
reqRequest
resResponse
Returns

void


setOpenAPIInfo

setOpenAPIInfo(info): void

Parameters

NameType
infoOpenAPIInfo

Returns

void


setOpenAPIJsonPath

setOpenAPIJsonPath(path): void

Parameters

NameType
pathstring

Returns

void


useController

useController(controller): Router

Utility Method to map a controller class be used by Express

Example

app.use('/', useController(UserController));

Parameters

NameTypeDescription
controlleranyController class

Returns

Router

Express Router object with associated routes

OpenAPI.json

You can further expand your OpenAPI documentation with a static file that includes most fields in the OpenAPI Object (see below). By default the system will look for <rootdir>/OpenAPI.json. This path can be overwritten to a custom location with setOpenAPIJsonPath(path: string). When this file is detected, calls to setOpenAPIInfo() and setOpenAPITags() will be ignored.

OpenAPI Object Reference

http://spec.openapis.org/oas/v3.1.0

export type OpenAPI = {
    openapi: string
    info: OpenAPIInfo
    jsonSchemaDialect?: string
    servers?: OpenAPIServers[]
    paths?: OpenAPIPath
    webhooks?: {
        [key: string]: OpenAPIPathItem | OpenAPIReference
    }
    components?: OpenAPIComponents
    security?: OpenAPISecurityRequirement
    externalDocs?: OpenAPIExternalDocumentation
    tags?: OpenAPITag[]
};

OpenAPI.json example

./OpenAPI.json

{
    "info": {
        "title": "Example Decorator API",
        "summary": "A sample Express Application which demostrates the decorator usage",
        "description": "This sample Express Application demonstrates usage of decorators to create Express Routers called 'controllers' from Classes.  Methods of the class can represent the routes of the Express Application when correctly decorated.  Optionally, OpenAPI documentation can be generated to describe the API.",
        "contact": {
            "name": "Paul Forbes",
            "email": "paulforbes42@gmail.com"
        },
        "license": {
            "name": "Apache 2.0",
            "identifier": "Apache-2.0",
            "url": "https://www.apache.org/licenses/LICENSE-2.0.html"
        },
        "version": "0.0.1"
    }
}

Development

NPM scripts to assist in development of this project.

  • npm run build clean up branch, run unit tests and lint to prepare for PR
  • npm run clean remove the dist directory
  • npm run dev run the TypeScript compiler in watch mode
  • npm run docs generate Typedoc site in docs directory
  • npm run lint execute eslint validation
  • npm run test execute unit tests
0.3.0

2 years ago

0.2.0

2 years ago

0.1.4

2 years ago

0.1.5

2 years ago

0.1.3

2 years ago

0.1.2

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago