Skip to content

Commit cf18bc1

Browse files
committed
first version of Swagger-jack article
1 parent 3912e74 commit cf18bc1

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
---
2+
layout: post
3+
title: Swagger-jack: unleash your API
4+
tags: [swagger, api, validation, nodejs, express, descriptor]
5+
author: feugy
6+
---
7+
8+
Perhaps did you already heard about [Swagger](http://developers.helloreverb.com/swagger/). And if not, I can only beg you to drop an eye on it.
9+
10+
Swagger is a specification and complete framework implementation for describing, producing, consuming, and visualizing RESTful web services.
11+
12+
It provides:
13+
- specification: to write descriptors of your API
14+
- tools: based on this descriptors: friendly GUI for documentation, client libraries...
15+
16+
**Swagger-jack** is one of these tools: a couple of [Express](http://expressjs.com/) middelware (the famous [NodeJS](http://nodejs.org/) Web framework) to generate your own API, and take advantage of automated input validation.
17+
18+
You'll find the source code on [github](https://github.com/feugy/swagger-jack), and the project was released on [NPM](https://npmjs.org/package/swagger-jack)
19+
20+
<br/>
21+
## What is swagger
22+
23+
Whether you're building huge information systems or providing a single (but powerfull) REST web service, describing your API will give a better knowledge and therefore usage of your service. And If you can benefit from a well-known standard and its tooling suite... It's icing on the cake.
24+
25+
![swagger-ui example](http://helloreverb.com/img/swagger-hero.png)
26+
27+
Swagger is mainly a specification. You'll find it on [github](https://github.com/wordnik/swagger-core/wiki/Resource-Listing).
28+
29+
In respect of the REST conventions your API will expose **resources**.
30+
A resource is one of your application concepts, and is bound to a path (the root of an url).
31+
32+
{% highlight json %}
33+
{
34+
"apiVersion": "2.0",
35+
"basePath": "http://in.api.smartdata.io",
36+
"apis": [{
37+
"path": "/api-docs.json/source"
38+
},{
39+
"path": "/api-docs.json/stream"
40+
},{
41+
"path": "/api-docs.json/preprocessor"
42+
}]
43+
}
44+
{% endhighlight %}
45+
46+
The entry point of your descriptor (available at `/api-docs.json`) is therefore a list of resources and their paths.
47+
You've certainly noticed that the format used is JSON ;).
48+
Each resource leads to a detailed descriptor (available at `/api-docs.json/resource-name`)
49+
50+
{% highlight json %}
51+
{
52+
"apiVersion": "2.0",
53+
"basePath": "http://in.api.smartdata.io",
54+
"apis": [{
55+
"path": "/source",
56+
"operations": [{
57+
"httpMethod": "GET",
58+
"nickname": "list",
59+
"summary": "list the sources with pagination",
60+
"responseClass": "SourceResults",
61+
"parameters": [{
62+
"name": "from",
63+
"description": "offset results will start from",
64+
"dataType": "int",
65+
"paramType": "query"
66+
},{
67+
"name": "size",
68+
"description": "number of results returned",
69+
"dataType": "int",
70+
"paramType": "query"
71+
},{
72+
"name": "query",
73+
"description": "find sources matching a query",
74+
"dataType": "string",
75+
"paramType": "query"
76+
}]
77+
}]
78+
},{
79+
"path": "/source/{id}",
80+
"operations": [...]
81+
},{
82+
"path": "/source/{id}/fields",
83+
"operations": [...]
84+
},{
85+
"path": "/source/{id}/mapping-preview",
86+
"operations": [...]
87+
}]
88+
}
89+
{% endhighlight %}
90+
91+
For a given resource, a detailed descriptor will give a list of **api**.
92+
An api is simply a sub-path associated with a list of **operations**.
93+
An operation is an HTTP verb for this sub-path, a set of awaited parameters and an expected model for the response.
94+
95+
Last of all, the detailed descriptor will embed a list of **models**.
96+
A model is a formal description of a complex object, that can be used in input parameters and output response body.
97+
98+
{% highlight json %}
99+
"models": {
100+
"Source": {
101+
"id": "Source",
102+
"properties": {
103+
"_id": {
104+
"type": "string",
105+
"description": "source identifier of 24 characters"
106+
},"created": {
107+
"type": "string",
108+
"description": "creation date (format ISO8601)"
109+
}, "tags": {
110+
"type": "array",
111+
"items": {
112+
"type": "string"
113+
},
114+
"description": "used to store custom informations. Only string values are allowed."
115+
},"preprocessors": {
116+
"type": "array",
117+
"description": "There are a lot of preprocessors, and each preprocessor describe itself.",
118+
"items": {
119+
"$ref": "SourcePreProcessor"
120+
}
121+
}
122+
}
123+
}
124+
{% endhighlight %}
125+
126+
Models are described in [json-schema](http://json-schema.org/), an emerging (and already well known) standard to describe the expected content of a complex JSON object.
127+
128+
<br/>
129+
To sum up, each urls of your REST web service will be grouped within operations (same url, different http methods), organized in apis (all sub-path of a base url) and resources (your application is a list of resources)
130+
131+
<br/>
132+
## The swagger-jack library: why and how
133+
134+
We heavily use NodeJS in our project, and Express is the community most popular web framework.
135+
It's principle is quite simple: you declare your routes (an urls and an http method) and associate each of them to a function with specific arguments.
136+
Second concepts: middleware.
137+
A middleware is a function that behave like Java's filters: it's invoked for each incoming request and can process to it, enrich it and let other process it, or just ignore it.
138+
139+
We wanted to use swagger on existing web services, and enforce the input validation.
140+
We looked at swagger-node-express the official nodejs plugin provided, but it involved too many code changes, and it does not provide validation.
141+
And that's how swagger-jack was born.
142+
143+
It provides three middleware, which you can enable or not.
144+
145+
{% highlight json %}
146+
var express = require('express'),
147+
swagger = require('swagger');
148+
149+
var app = express();
150+
151+
app.use(express.bodyParser())
152+
.use(express.methodOverride())
153+
.use(swagger.generator(app, {
154+
// general descriptor part
155+
apiVersion: '2.0',
156+
basePath: 'http://my-hostname.com/api'
157+
}, [{
158+
// descriptor of a given resource
159+
api: {
160+
resourcePath: '/user'
161+
apis: [{
162+
path: '/api/user/'
163+
operations: [{
164+
httpMethod: 'POST',
165+
nickname: 'create'
166+
}, {
167+
httpMethod: 'GET',
168+
nickname: 'list'
169+
}]
170+
}]
171+
},
172+
// controller for this resource
173+
controller:
174+
create: function(req, res, next) {
175+
// create a new user...
176+
},
177+
list: function(req, res, next) {
178+
// list existing users...
179+
}
180+
}])
181+
.use(swagger.validator(app))
182+
.use(swagger.errorHandler())
183+
184+
app.get "/api/unvalidated", function(req, res, next) {
185+
// not documented nor validated
186+
}
187+
app.listen(8080);
188+
{% endhighlight %}
189+
190+
### Generator middleware
191+
192+
Generator takes a general descriptor path (which is totally not constraint: put whatever you need in it), and an array of "resources".
193+
194+
The middleware will automatically register to your express application the routes found inside the descriptor, and bound them to the provided controller (it uses the `nickname` attribute to reach your function). In this example, two routes are created:
195+
196+
1. `POST /api/user/` to create a user (controller method `create()`)
197+
2. `GET /api/user/` to list existing users (controller method `list()`)
198+
199+
You can still register routes and middleware within your application, but they will not be documented nor validated.
200+
201+
### Validator middleware
202+
203+
Validator will analyze the declared parameters of your descriptor, and validate the input.
204+
It will handle parameter casting, range validation and declared model compliance (thank to the excellent [json-gate](https://github.com/oferei/json-gate)).
205+
206+
All casted values (except body parameters) are available inside the controller methods with the `req.input` associative array.
207+
No matter if parameter is from path, query or header: it will be present inside `req.input`.
208+
209+
But you can still use the Express original function (beware: values are just strings).
210+
211+
Body is just validated, as it was already parsed into json by the `express.bodyParser` middleware.
212+
213+
If you do not need validation, no problem: just remove the validator middleware.
214+
215+
### Error middleware
216+
217+
Validation errors (and your custom business errors) are handled by the error middleware.
218+
It uses the express's error mecanism: invoke the next() method with an argument.
219+
220+
Weither it's a string or an object, it will be serialized into a json response with an http status (500 by default).
221+
222+
For example:
223+
224+
{% highlight json %}
225+
.use(swagger.generator(app,
226+
{ // general descriptor ... }
227+
[{
228+
api: // resource descriptor...
229+
controller: {
230+
create: function(req, res, next) {
231+
if (// error check...) {
232+
var err = new Error('forbidden !');
233+
err.status = 403;
234+
return next(err);
235+
}
236+
// process ...
237+
}
238+
}
239+
}])
240+
{% endhighlight %}
241+
242+
Input validation errors are reported the same way.
243+
244+
You may not use the error middleware and provide your own.
245+
246+
### Last Power-tip !
247+
248+
Use [js-yaml](http://nodeca.github.com/js-yaml/) to store your descriptor in a separate file, and split your code into other controller modules:
249+
250+
{% highlight json %}
251+
var express = require('express'),
252+
swagger = require('swagger'),
253+
yaml = require('js-yaml');
254+
255+
var app = express();
256+
257+
app.use(express.bodyParser())
258+
.use(express.methodOverride())
259+
.use(swagger.generator(app,
260+
require('/api/general.yml'),
261+
[{
262+
api: require('/api/users.yml'),
263+
controller: require('/controller/users')
264+
},{
265+
api: require('/api/commands.yml'),
266+
controller: require('/controller/commands')
267+
}])
268+
.use(swagger.validator(app))
269+
.use(swagger.errorHandler())
270+
271+
app.listen(8080);
272+
{% endhighlight %}
273+
274+
<br/>
275+
## In conclusion
276+
277+
Swagger-jack enpowered your NodeJS application with Swagger compliant Api descriptor.
278+
279+
It brings you a better lisibility: first you describe things (even in a separate file thanks to js-yaml), then you implement them.
280+
281+
It respects your own code organization: whether to use a huge file or one file per url is your choice.
282+
283+
It helps you secure your code: syntax validation (parameter existence, type, occurence, allowed value) is automatically done based on your descriptor.
284+
Even the head-crusher body validation, for POST and PUT requests. Thanks to json-schema, it became as easy as pie.
285+
You can then focus on semantic and business validation.
286+
287+
At last, it opens you the doors to the swagger's galaxy: documentation generator, automatic requesters, client library generators...
288+
289+
So, have fun with swagger and swagger-jack !
290+
291+
--------
292+
### Addendum: what's with that name ?
293+
294+
We looked for a fun and yet eloquent name. But swagger.js was already used.
295+
[Jack Swagger](http://www.wwe.com/superstars/jackswagger) is an american catch superstar, and we never heard about him before, but it perfectly fits your naming goals :)

0 commit comments

Comments
 (0)