/headlamp

Generate express api documentation automagically

Primary LanguageJavaScript

@clearc2/headlamp

Generate express api documentation automagically.

Requirements

This issues grep commands to search code. If you would like to use the source code searching feature, your system must have grep installed.

Install

yarn add -D @clearc2/headlamp

Create a new routes directory.

server/
  routes/
  server.js

In your express api bootstrap file:

// server/server.js

import express from 'express'
import bodyParser from 'body-parser'
import path from 'path'
import fs from 'fs'
import cors from 'cors'
import shine from '@clearc2/headlamp'

const app = express()
app.use(cors())
app.use(bodyParser.json())

// old routes here

shine(app, {
  title: 'IOP API Docs',
  description: String(fs.readFileSync(path.resolve(__dirname, '..', 'readme.md'))),
  routes: path.resolve(__dirname, 'routes'),
  src: path.resolve(__dirname, '..', 'src'),
  srcDir: '_headlamp',
  server: [
    path.resolve(__dirname),
    path.resolve(__dirname, '..', 'src', 'redux', 'server')
  ],
  hidePath: path.resolve(__dirname, '..'),
  headers: {
      Authorization: {
          value: 'foobar',
          help: 'jwt auth token'
      },
      'X-Custom-Thing': 'abc'
  },
  responses: [
    {
      title: 'Invalid request',
      status: 400,
      response: {
        errors: [
          {error: 'Invalid request'}
        ]
      }
    },
    {
      title: 'Server error',
      status: 500,
      response: {
        errors: [
          {error: 'Something went wrong.'}
        ]
      }
    }
  ]
})

const PORT = process.env.PORT || 5033
app.listen(PORT, () => {
  console.log('Dev Express server running at localhost:' + PORT)
})

After starting your mock server, you will have an api explorer at http://localhost:<port>/_docs.

Configuration

The second argument to the shine function is a configuration object. All are optional.

title

Turns into the header of the api explorer.

description

General api description/documentation. Supports markdown.

routes

The directory to the route files. See route file documentation below.

src

The source code path. Can be a single path or array of paths.

srcDir

The name of directories within your src where route files are located.

server

The mock server path. Can be a single path or array of paths.

hidePath

Removes the path from the file names in the api explorer.

headers

Headers to send for every request. See params documentation below for similar formatting.

responses

Default responses that will be associated with every file route. Useful for documenting error responses. Should be an array of response objects. These will only be applied to routes defined via route files. See the responses documentation below.

Route files

This package gives you a new way to define mock endpoints through route files. Route files can be nested in the routes directory. Example:

server/
  routes/
    auth-info.js
    companies/
      get-companies.js
      save-company.js
  server.js

The files will automatically get incorporated into the mock server as functional endpoints.

Structure

Route files can either be a .js file that exports an object or a .json file. See this example route file.

// server/routes/companies/employees.js

export default {
  path: '/companies/:companyUnid/employees',
  methods: ['GET'],
  title: 'Company employees',
  description: 'Fetches all active employees',
  response: {
    employees: [
      {
        name: 'John Doe'
      },
      {
        name: 'Bob Smith'
      }
    ]
  }
}

The equivalent json file of the above would look like below and behave identically.

{
  "path": "/companies/:companyUnid/employees",
  "methods": ["GET"],
  "title": "Company employees",
  "description": "Fetches all active employees",
  "response": {
    "employees": [
      {
        "name": "John Doe"
      },
      {
        "name": "Bob Smith"
      }
    ]
  }
}

path

This must be a valid express route path.

method

A single http verb that this endpoint responds to. Ex. POST

methods

Routes can respond to multiple http methods. This must be an array of http verbs that this endpoint responds to. Ex. ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']

title

Only used for documentation in the api explorer ui.

description

Only used for documentation in the api explorer ui. Supports markdown.

params

URL params are the tokens prefixed by : in the path. These params can be further defined:

export default {
  path: '/companies/:companyUnid/employees',
  params: {
    companyUnid: 'abcdef'
  }
  // ...
}

The above will prepopulate this param's input in the api explorer. You can provide additional help text by using an object.

export default {
  path: '/companies/:companyUnid/employees',
  params: {
    companyUnid: {
      value: 'abcdef',
      help: 'Company Unid'
    }
  }
  // ...
}

The help text will show up directly beneath the param's input in the api explorer.

query

Query params can be defined in the same way.

export default {
  path: '/companies/:companyUnid/employees',
  query: {
    hiredAfter: {
      value: '01-01-2018',
      help: 'Format MM-DD-YYYY'
    }
  }
  // ...
}

headers

Headers to send with this request. Same format as params and query.

payload

POST, PUT, and PATCH methods allow for example payloads. These payloads will populate the payload input in the api explorer.

export default {
  path: '/contact',
  methods: ['POST'],
  payload: {
    contact: {
      firstName: 'John',
      lastName: 'Doe'
    }
  }
  // ...
}

In the case of endpoints accepting both POST and PUT requests, you can define unique payloads for both.

export default {
  path: '/contact',
  methods: ['POST', 'PUT'],
  payload: {
    POST: {
      contact: {
        firstName: 'John',
        lastName: 'Doe'
      }
    },
    PUT: {
      contact: {
        id: 'abcded',
        firstName: 'John',
        lastName: 'Doe'
      }
    }
  }
  // ...
}

response

Responses can be simple javascript objects...

export default {
  path: '/people',
  methods: ['GET'],
  response: {
    people: {
      [
        {name: 'John Doe'},
        {name: 'Bob Smith'}
      ]
    }
  }
}

Or come from fixture files...

export default {
  path: '/people',
  methods: ['GET'],
  response: require('./_people.json')
}

Or dynamically generated based on the request...

export default {
  path: '/people',
  methods: ['GET', 'POST', 'PUT'],
  response: (req, res) => {
    if (req.method === 'POST') {
      return res.status(400).json({
        errors: [
          {detail: 'Something went wrong :('}
        ]
      })
    }

    if (req.method === 'PUT') {
      return res.status(500).json({
        errors: [
          {detail: 'CRITICAL FAILURE'}
        ]
      })
    }

    return res.status(200).json(require('./_people.json'))
  }
}

Unless a function is provided, a 200 status code will be sent.

responses

Instead of, or in conjunction with, the response key, you can provide static responses.

export default {
  path: '/people',
  method: 'POST',
  responses: [
    {
      title: 'All good',
      description: 'Some **markdown** describing this response',
      status: 200,
      response: {
        foo: 'bar'
      }
    },
    () => ({
      title: 'Unauthorized',
      status: 401,
      response: require('./_bigData') // large fixture that doesn't need to be loaded into memory right away
    }),
    {
      title: 'No bueno',
      status: 500,
      response: {
        errors: [
          {error: 'Something went wrong :('}
        ]
      }
    }
  ]
}

These responses will be displayed in the api explorer. Because of that, they do not have access to the express req or res objects. Zero arguments are passed to functional responses. If you need to add a delay or dynamically generate a response based on the request, use response.

globalHeaders

Boolean to determine whether to merge the globally defined headers to this route. Defaults true.

globalResponses

Boolean to determine whether to concat the globally defined responses to this route. Defaults true.

Response objects

Static responses must either be an object or a function that returns an object. Static response objects have the following fields:

title

The title of the static response.

description

The description of the static response. Supports markdown.

status

The status http status code.

response

The response payload.

Response resolution

The following priority is used to determine which response to use.

  • If activated response, use it
  • If response object/function exists, use it
  • If first response in responses exists, use it
  • Sends error response

Fixtures

Fixture files can be nested in the routes directory but their file names must be prefixed with an underscore to not be interpreted as a route file.

server/
  routes/
    people/
      get-people/
        _people.json     # will not be interpreted as a route file
        route.js         # will be interpreted as a route file
      save-person.js     # will be interpreted as a route file
      update-person.json # will be interpreted as a route file

Explorer features

Search

Searching in the frontend is case-insensitive. You can omit path variables and simply provide a colon. Ex.

/companies/:/employees/:/address

The above would match:

/companies/:companyUnid/employees/:unid/address