This is a tutorial which will teach you how to create an OpenAPI 3.0.3 document, and host it with Exegesis on node.js. You can find complete source for this tutorial in the samples directory, in both JavaScript and TypeScript.
OpenAPI 3.0.3 is the successor to Swagger - version 2.0 was known as the Swagger Specification. While there are a few choices for implementing OpenAPI 2.0/Swagger on node.js, Exegesis is the first complete server-framework for implementing version 3.0.X of the spec.
First, let's create the scaffold of our project:
mkdir exegesis-tutorial
cd exegesis-tutorial
mkdir controllers
npm init -y
npm install express exegesis-express
This creates a project folder named "exegesis-tutorial", a sub-folder named "controllers" (where we'll put our controller implementations - the code that gets run when someone accesses our API), creates a package.json file, and installs the dependencies we'll need (express and exegesis-express).
The heart of any OpenAPI-based API is the OpenAPI document, which describes all the paths and parameters your application accepts. We'll store this in a file called "openapi.yaml". This is the simplest OpenAPI 3.0.3 document you can write:
openapi: 3.0.3
info:
title: My API
version: 1.0.0
paths:
The openapi: 3.0.3
part tells us this is an OpenAPI document, and conforms
to version 3.0.3
of the spec. There's a "paths" section where we can list all the paths our API
exposes.
This has all the required fields to be an OpenAPI document, and will pass validation, but as API documents go, this one is pretty boring. It doesn't actually do anything. Let's fix that by adding a path:
openapi: 3.0.3
info:
title: My API
version: 1.0.0
paths:
'/greet':
get:
summary: Greets the user
operationId: getGreeting
x-exegesis-controller: greetController
parameters:
- description: The name of the user to greet.
name: name
in: query
required: true
schema:
type: string
responses:
200:
description: A greeting for the user.
content:
application/json:
schema:
type: object
required:
- message
properties:
message:
type: string
default:
description: Unexpected error.
content:
application/json:
schema:
type: object
required:
- message
properties:
message:
type: string
That got big fast! Let's break this down into parts.
We've added a new "/greet" to our "paths" section, with a "get" operation:
paths:
'/greet':
get:
summary: Greets the user
operationId: getGreeting
x-exegesis-controller: greetController
This means clients can send an HTTP GET to "/greet" to run this operation. This
operation also has an operationId
of "getGreeting". This is a string that
uniquely identifies this operation, across the entire document. No other
operation is allowed to have this operationId.
There's also one extra special part we've added here, the "x-exegesis-controller". Anything that starts with an "x-" in an OpenAPI document is called a "specification extension". In this case, we're using an Exegesis-specific extension to tell Exegesis what JS module contains the code for this controller.
The get operation also has one parameter, the "name" parameter:
parameters:
- description: The name of the user to greet.
name: name
in: query
required: true
schema:
type: string
This parameter must be present, and must be a string. Exegesis will generate a validation error back to the client if this field isn't present, or is the wrong type.
Finally, there's a responses section:
200:
description: A greeting for the user.
content:
application/json:
schema:
type: object
required:
- message
properties:
message:
type: string
This says we can reply with a 200 response, and if we do, the response is going to be a JSON object with a single property, "message". There's also a "default" response, which is what the client can expect if our response is not a 200 response. You can list as many different response codes here as you wish.
Now that we have a simple OpenAPI document, we need to have a server which implements it. Starting with the existing project, save the above OpenAPI document as "openapi.yaml". Then create an index.js file. This file is mostly boilerplate:
const express = require('express');
const exegesisExpress = require('exegesis-express');
const http = require('http');
const path = require('path');
async function createServer() {
// See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
const options = {
controllers: path.resolve(__dirname, 'controllers'),
allowMissingControllers: false,
};
// This creates an exegesis middleware, which can be used with express,
// connect, or even just by itself.
const exegesisMiddleware = await exegesisExpress.middleware(
path.resolve(__dirname, './openapi.yaml'),
options
);
const app = express();
// If you have any body parsers, this should go before them.
app.use(exegesisMiddleware);
// Return a 404
app.use((req, res) => {
res.status(404).json({ message: `Not found` });
});
// Handle any unexpected errors
app.use((err, req, res, next) => {
res.status(500).json({ message: `Internal error: ${err.message}` });
});
const server = http.createServer(app);
return server;
}
createServer()
.then(server => {
server.listen(3000);
console.log('Listening on port 3000');
console.log('Try visiting http://localhost:3000/greet?name=Jason');
})
.catch(err => {
console.error(err.stack);
process.exit(1);
});
The interesting bit here is really the options we pass to Exegesis:
const options = {
controllers: path.resolve(__dirname, 'controllers'),
allowMissingControllers: false,
};
controllers
gives the path to the "controllers" folder, where we store our
controller implementations. allowMissingControllers: false
tells Exegesis
to throw an error at startup if any of our paths don't have a controller.
There are lots of other handy options
you can pass here. Note that if you want to enable response validation,
you must pass the onResponseValidationError
option.
Now we have an OpenAPI document, and we have the boilerplate that starts the server, but the exciting part is the "controller" - this is the code that actually implements our OpenAPI document. You may recall in our OpenAPI document we specified:
operationId: getGreeting
x-exegesis-controller: greetController
So now were going to use the folder named "controllers" that was created when you initially created the project. In that folder we're going to create a file called "greetController.js":
// This function has the same name as an operationId in the OpenAPI document.
exports.getGreeting = function getGreeting(context) {
const name = context.params.query.name;
return { message: `Hello ${name}` };
};
This controller is pretty simple - is just reads in the name parameter, and
returns a JSON object. Controllers can optionally return a Promise, take a
callback as a second parameter, or even just write a response directly
to context.res
.
The context
variable here is an "Exegesis Context",
which contains lots of helpful info
for when you're writing a controller.
Start the server with:
node index.js
Then, try pointing your browser at http://localhost:3000/greet?name=Jason, and you should see a greeting!