Skip to content

Commit 9fe2b15

Browse files
committed
Merge pull request #22 from feugy/master
New article: swagger-jack
2 parents 3912e74 + f03854b commit 9fe2b15

File tree

1 file changed

+296
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)