Automatically create Swagger documentation and Express endpoints from TypeScript classes. The tool supports
- Automatically inferring types from TypeScript function declarations
- Generating callable service endpoint for Express
- Reading class, interface and method and JSDoc comments into Swagger docs
- Defining parameter and return value Models using TypeScript
- Defining types returned at specific error codes
- Grouping functions using Tags
Request
andResponse
as private Service object members (does not pollute interface signature)- Rewrites only functions, not entire files
- Optional values support
- Private service methods
At command line you run
ts2swagger <directory>
Then the tool will
- Locate all classes with JSDoc comment
@service <serviceid>
- Locate all intefaces or classes with JSDoc comment
/** model true */
- It will create Swagger documentation specified with
@swagger <filename>
- It will locate all functions having comment
@service <serviceid>
and create express endpoint in them
It is possible to automate creation of interface with tools like nodemon etc.
npm -g i ts2swagger
@swagger <filename>
output Swagger (OpenAPI 2) file@title <document title>
title of the service@service <serviceid>
used to identify service@endpoint <path>
path where service is located@version <versionnumber>
for example 1.0.0 or something
/**
* Freeform test of the API comes here
*
* @swagger /src/swagger/sample.json
* @title The title of the Doc
* @service myserviceid
* @endpoint /sometest/v1/
* @version 1.0.1
*/
export class MyService {
constructor(private req: express.Request, private res: express.Response) {}
ping(message: string): string {
return `you sent ${message}`;
}
async getDevices(): Promise<Device[]> {
return [];
}
}
It will compile that file into Swagger JSON file /src/swagger/service.json
and write the
Express endpoint if it finds a file with a function which is declared using the service ID
In the service / interface parameters are mapped to GET, POST, PUT, DELETE or PATCH methods automatically by detecting their value and with some information from the JSDoc comments.
@alias <path>
the url where this method is located@queryparam <name>
is the first parameter to be placed to GET query var?varname=value
@method <httpMethod>
used http method GET, POST, PUT, DELETE or PATCH@tag <name>
group where this function belongs@tagdescription <text>
description for the tag (multiple occurrence override each other)@error <code> <model>
HTTP response code and message which is returned
/**
*
* @alias user
* @method delete
* @param id set user to some value
* @tag user
* @tagdescription System users
* @error 401 MyErrorClass
* @error 404 MyErrorClass
*/
deleteUser(id:string) : TestUser {
return {name:'foobar'}
}
You can declare methods private in TypeScript and they will not be part of the
private getUser() {
return this.req.user
}
If interface is returning models
@model true
indicate model is ment to be used with services
example
/**
* @model true
*/
export class Device {
id: number;
name: string;
description?: string;
}
import * as express from "express";
const app = express();
/**
* @service myserviceid
*/
function bootstrap(app: any, server: (req, res) => MyService) {
// The code will be generated in here
}
After running ts2swagger
the function bootstrap
will be overwritten to have contents
function bootstrap(app: any, server: (req, res) => MyService) {
// Service endpoint for ping
app.get("/sometest/v1/ping/:message/", async function(req, res) {
try {
res.json(await server(req, res).ping(req.params.message));
} catch (e) {
res.status(e.statusCode || 400);
res.json(e);
}
});
}
It is almost ready to be used as a service, but you have to start it by adding line
bootstrap(app, (req, res) => new MyService(req, res));
// and start listening to some port with express
app.listen(1337);
console.log("listening on port 1337");
The if you navigate to http://localhost:1337/sometest/v1/ping/hello
you get
"you sent hello"
You will also get Swagger documentation in JSON
Define Error model, which must have statusCode
set to the HTTP status code value.
/**
* @model true
*/
export class ErrorNotFound {
statusCode = 404;
message?: string;
}
That class can then be thrown in the service handler
if (somethingwrong) throw ErrorNotFound();
The Inteface declaration has error defined like this
/**
* @error 403 ErrorForbidden
* @error 404 ErrorNotFound
*/
async hello(name:string) : Promise<string> {
if(name==='foo') throw { errorCode:404, message:'User not found'}
return `Hello ${name}!!!`
}
File upload must be handled manually at the moment, no server code is generated for it. Swagger documentation can have three variables
@upload
which defines the name of the uploaded file variable in form data@uploadmeta
is name for text field which can contain encoded JSON metadata@uploadmetadesc
Documentation for the uploadmeta contents
/**
* @method post
* @upload file
* @uploadmeta into
* @uploadmetadesc send JSON encoded string here...
*/
async upload(): Promise<any> {
// handle upload params manually
}
This is example whith is bootstrap from src/backend
sample code and includes function
named bootstrap
which is generated from the src/backend/sample.ts
and used as both
- Functional backend service
- Swagger Document served by
swagger-ui-express
Also the public folder is served as static so you can begin developing the client app based on the service.
NOTE: only the function bootstrap
in the example will be overwritten by ts2swagger
,
you can manually edit other parts of the code as a programmer.
import * as express from "express";
import { MyService } from "./sample";
const app = express();
const bodyParser = require("body-parser");
app.use(bodyParser.json());
app.use(express.static("public"));
const swaggerUi = require("swagger-ui-express");
// sample server...
app.use(
"/api-docs",
swaggerUi.serve,
swaggerUi.setup(require("../../swagger/sample.json"))
);
type serverFactory = (req, res) => MyService;
/**
* @service myserviceid
*/
function bootstrap(app: any, server: serverFactory) {
// Automatically generated endpoint for ping
app.get("/sometest/v1/ping/:message/", async function(req, res) {
try {
res.json(await server(req, res).ping(req.params.message));
} catch (e) {
res.status(e.statusCode || 400);
res.json(e);
}
});
// Automatically generated endpoint for sayHello
app.get("/sometest/v1/hello/:name/", async function(req, res) {
try {
res.json(await server(req, res).sayHello(req.params.name));
} catch (e) {
res.status(e.statusCode || 400);
res.json(e);
}
});
// Automatically generated endpoint for getDevices
app.get("/sometest/v1/getDevices/", async function(req, res) {
try {
res.json(await server(req, res).getDevices());
} catch (e) {
res.status(e.statusCode || 400);
res.json(e);
}
});
// Automatically generated endpoint for upload
app.post("/sometest/v1/upload/", async function(req, res) {
try {
res.json(await server(req, res).upload());
} catch (e) {
res.status(e.statusCode || 400);
res.json(e);
}
});
}
// initialize the API endpoint
bootstrap(app, (req, res) => new MyService(req, res));
if (!module.parent) {
app.listen(1337);
console.log("listening on port 1337");
}
MIT.