npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ts2swagger

v0.0.33

Published

Convert TypeScript classes into Swagger docs and Express API

Downloads

17

Readme

TypeScript to Swagger

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 and Response as private Service object members (does not pollute interface signature)
  • Rewrites only functions, not entire files
  • Optional values support
  • Private service methods

How it works?

At command line you run

ts2swagger <directory>

Then the tool will

  1. Locate all classes with JSDoc comment @service <serviceid>
  2. Locate all intefaces or classes with JSDoc comment /** model true */
  3. It will create Swagger documentation specified with @swagger <filename>
  4. 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.

Installing

npm -g i ts2swagger

Service class JSDoc params

  • @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

inteface/class flags

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'}
  }

Private methods

You can declare methods private in TypeScript and they will not be part of the

  private getUser() {
    return this.req.user
  }

Model intefaces / class flags

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;
}

Express bootstrap definition

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

Handling Errors

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}!!!`
  }

Upload parameters

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
  }

Full Generated Frontend Sample

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

  1. Functional backend service
  2. 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");
}

License

MIT.