This tutorial is out of date and no longer maintained.
So it happened I started this project with a friend of mine, a new app to launch on the mobile market. I offered to write the server-side, a simple web API. Once he got my code he immediately asked me “Sam how do I know the routes and so on?! Write a documentation or something!”. And then… Swagger came up.
I presume many of you encountered this situation before or will in the future, a developer may write the best web API service but without proper documentation how can other developers discover and use it?
So in this tutorial, I want to introduce Swagger, a famous open-source framework to help you write RESTful APIs.
Swagger offers:
In addition, it is supported by many programming languages.
One feature I like is the mock mode, which allows developers to design routes without writing a single line of code in javascript.
This is the approach I am going to follow in this tutorial: We first focus on the design and then on the code.
In the tutorial, we are going to create a RESTful API to manage our movies collection.
You can directly clone the app repository from my GitHub here.
Let’s move to the installation of Swagger.
As said before Swagger is supported in Node.js through its own module, here is the link to the GitHub project: The documentation is pretty straight-forward to help beginners understand and configure Swagger.
Once we open the command line we install the module (global).
- npm install -g swagger
Now let’s create our project.
- swagger project create movie-collection
The prompt asks for the framework we want to use, choose express. Once completed, let’s take a look at the project directory:
-- api
---- controllers
------ hello_world.js
---- helpers
---- mocks
---- swagger
------ swagger.yaml
-- config
---- default.yaml
-- test
---- api
------ controllers
-------- hello_world.js
------ helpers
-- app.js
-- package.json
Each subfolder contains a README.md
that for simplicity I didn’t include above.
/swagger
folder which contains the file swagger.yaml
, an important file we are going to edit to define everything related to the project information and routes. Throughout the tutorial, I am going to explain it all so don’t worry for now.default.yaml
that, as the documentation states, drives the application’s config directory. Though we are not going to customize the file I still suggest you read the related documentation to understand the engine beneath the “magic” of declaring APIs without writing a line of code but through YAML or JSON.app.js
is the main file that runs the server.
Have you noticed that there is already a controller called hello_world.js
? Whenever you create a new project, the module adds an example route, a GET request to /hello
which takes a name as a parameter and greets the person. Not original at all, I give you that, but as we are beginners it is good to see Swagger in action and get insight on how it works.
What about taking a look at the example in action?
In order to run the example, I have to introduce Swagger editor.
Swagger editor is an elegant browser-based editor which really simplifies our efforts to develop a web API. In particular, it provides:
The picture above shows you the UI of the Swagger editor of our app. On the left, you can see the YAML file we are going to edit (swagger.yaml
remember?) while on the right is the list of routes. By clicking on any of them we can have a good understanding of the parameter required, the format of requests and responses, more generally a description of the route.
Now, to launch the example, start the app by running:
- swagger project start
Whenever we modify a file, it automatically restarts the server.
Then, open a second command line and launch the editor with:
- swagger project edit
If everything went well the editor should open a new tab in your browser. On the right side of the page you should notice the example path for a GET request to /hello
, so open the tab and, at the bottom, click on the button try this operation.
We are asked to enter a parameter name: Go for it and test the route. Here is my result:
![](https://scotch-res.cloudinary.com/image/upload/media/144/DbOB9pBAQp666kvH00Vp_example final.png)
Let’s look at the YAML file on the left, precisely on the path part:
paths:
/hello:
# binds a127 app logic to a route
x-swagger-router-controller: hello_world
get:
description: Returns 'Hello' to the caller
# used as the method name of the controller
operationId: hello
parameters:
- name: name
in: query
description: The name of the person to whom to say hello
required: false
type: string
responses:
"200":
description: Success
schema:
# a pointer to a definition
$ref: "#/definitions/HelloWorldResponse"
# responses may fall through to errors
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
This short piece of YAML is what we need to define our routes:
x-swagger-router-controller
: This is the controller, the file we have in the /api/controllers/hello_world.js
.
Then the HTTP methods must be listed, in this case, just a GET.
operationId
: This refers to the function, in the controller, in charge of the business logic.
parameters
: The list of required parameters are defined here. The parameter name
is the only one and you may see that it is in
the path, it is not required
and it is a string
.
When it comes to the responses, Swagger shows its potential:
We can define a different response for each situation, given the HTTP status or errors, a particular response can be defined. In the example, for the status "200"
there is a pointer $ref
to a response definition while for other statuses, we go to default
with its own $ref
.
Why is that? In order to keep the YAML file clean, we can define all our responses under definitions
while at the same time reusing the definition for different responses.
Before we finish with the YAML file, let’s take a look at one more piece of code:
consumes:
- application/json
# format of the responses to the client (Accepts)
produces:
- application/json
On the top of the file, it has defined what the routes consumes
and produces
, it is the format of the request parameters and related response. Those rules are currently applying to all the paths defined in the file. It is also possible to customize these rules for a single path or HTTP method by including these properties inside of it.
However, for the purpose of the tutorial, we will stick to application/json
for all the routes, so a single definition on top is more than enough.
Lastly, let’s take a look inside /api/controllers/hello_world.js
to check the function hello
:
function hello(req, res) {
var name = req.swagger.params.name.value || 'stranger';
var hello = util.format('Hello, %s', name);
res.json(hello);
}
Nothing exotic here, though you should notice that with Swagger the req
object has a new property swagger
which contains the parameters we defined.
In the next section, we finally start developing our app.
Working with mocks does not require any code to be written, just editing the YAML file, so we can focus on the design part first.
First of all, rerun the project adding the flag -m
to the command which tells Swagger to run in mock mode, then run the editor in the second window.
- swagger movie-collection start -m
The first route returns the complete list of movies in our collection.
Delete the example /hello
and add these lines of code:
/movie:
# our controller name
x-swagger-router-controller: movie
get:
description: get the movies list
# define the type of response for Success "200" and Error
responses:
"200":
description: Success
schema:
$ref: "#/definitions/GetMoviesListResponse"
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
First, we define our controller movie
but there is no need to define the operationId
in charge of the business logic. In addition, no need for the parameters. Take note, the successful response has a schema GetMoviesResponse
.
The definitions, as usual, are listed at the bottom of the file, so copy and paste this code:
GetMoviesListResponse:
required:
- movies
properties:
# The array of movies
movies:
type: array
items:
type: object
properties:
id:
type: string
title:
type: string
year:
type: number
The response is nothing but an array of movies that contain a unique id
, a title
, and the year
.
Swagger editor autosaves whatever we input, which is great for us, but I noticed that sometimes it gets stuck. So if that is the case for you, try to refresh the page.
Here is the code inside the editor which automatically created a better view on the right.
Now test the route and check the result:
Since we didn’t define any operationId
, in mock mode swagger returns a standard value according to the properties type.
Since id
and title
are strings, it returns “string” while for the year
, as a number, it returns 1.
For the record, if not satisfied by the standard values, it is always possible to define the mock controller in /api/mocks
and add an operationId
. Then, the business logic just returns the answer in the format you defined in the definitions, but you can customize the values for each property.
However, I am not going to cover this step because I want to stick to the promise of working with mocks without writing a single line of code.
Let’s create a route to add a new movie to the list. After get:
add the following YAML lines:
post:
description: add a new movie to the list
# movie info to be stored
parameters:
- name: title
description: Movie properties
in: body
required: true
schema:
$ref: "#/definitions/Movie"
responses:
"200":
description: Success
schema:
$ref: "#/definitions/GeneralResponse"
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
This time we have a parameter in the body which is simply the movie object with a title
and year
. Let’s add the schema in the definitions:
Movie:
type: object
properties:
title:
type: string
description: task object name
year:
type: number
description: task description
required:
- title
- year
We declared the two fields title
and year
as required.
What about the responses? The error response is always the same (reusability) while we defined a GeneralResponse
. So, copy this code in the definition
:
GeneralResponse:
type: object
properties:
success:
type: number
description: returns 1 if successful
description:
type: string
description: a short comment
required:
- success
- description
So to speak, we have a success field set to 1 and a description of the operations. This will be a common response for all our routes when we create or update a movie, the only difference will be the description text.
Let’s try to add a movie:
Here is the result:
Now let’s retrieve a single movie. This time we need the parameter in the path, precisely the unique id.
We first need to create a new path called /movie/{id}
so copy and paste the following inside of it:
/movie/{id}:
# our controller name
x-swagger-router-controller: movie
get:
description: get a movie
# define the type of response for Success "200" and Error
parameters:
- name: id
type: string
in: path
required: true
responses:
"200":
description: Success
schema:
$ref: "#/definitions/GetMovieResponse"
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
Notice that this time the parameter, in its own field in
has path
as value.
The successful response it’s the movie object itself, so we need to add GetMovieResponse
to the list of definitions:
GetMovieResponse:
required:
- id
- title
- year
properties:
id:
type: string
title:
type: string
year:
type: number
Movie:
type: object
properties:
title:
type: string
description: task object name
year:
type: number
description: task description
required:
- title
- year
Let’s try it, for the id
parameter, we can mock it with a fake one:
and here is the expected response:
Now it’s time to create the route to update a movie, given the id in the path and a new title and year in the body. Copy the following code in the editor:
put:
description: update a movie
# define the parameters
parameters:
- name: id
description: Movie id
type: string
in: path
required: true
- name: title
description: Movie properties
in: body
required: true
schema:
$ref: "#/definitions/Movie"
responses:
"200":
description: Success
schema:
$ref: "#/definitions/GeneralResponse"
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
I really like the order to define the parameters, no matter if they come from the body, path, header, etc. They are all consequently listed where in
assumes the correct value.
Notice the GeneralResponse
, the same schema we used for adding a new movie
So, update a file:
And the result:
The last operation, let’s delete a movie by a given id
.
Copy and paste the following code:
delete:
description: delete a movie
# define the parameters
parameters:
- name: id
description: Movie id
type: string
in: path
required: true
responses:
"200":
description: Success
schema:
$ref: "#/definitions/GeneralResponse"
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
There is no need to further discuss the YAML file, we simply define the parameter and the response is GeneralResponse
.
Try the operation:
Here is the result:
…And congratulations! We finished the design and now it’s about time to write some JavaScript code.
The first thing to implement is the data-storage logic. Well, for the purpose of the tutorial we are not going to use any real database (MySQL, Mongo…) but simply store the data in memory for the time the app runs.
We need to add a new package, crypto
, to create the unique id for any stored movie:
- npm install crypto --save
Let’s create a file db.js
in /config
. We are going to export a simple object which mocks the DB operations of add, retrieve, update, and delete movies.
Here is the code to copy
'use strict;'
//Include crypto to generate the movie id
var crypto = require('crypto');
module.exports = function() {
return {
movieList : [],
/*
* Save the movie inside the "db".
*/
save(movie) {
movie.id = crypto.randomBytes(20).toString('hex'); // fast enough for our purpose
this.movieList.push(movie);
return 1;
},
/*
* Retrieve a movie with a given id or return all the movies if the id is undefined.
*/
find(id) {
if(id) {
return this.movieList.find(element => {
return element.id === id;
});
}else {
return this.movieList;
}
},
/*
* Delete a movie with the given id.
*/
remove(id) {
var found = 0;
this.movieList = this.movieList.filter(element => {
if(element.id === id) {
found = 1;
}else {
return element.id !== id;
}
});
return found;
},
/*
* Update a movie with the given id
*/
update(id, movie) {
var movieIndex = this.movieList.findIndex(element => {
return element.id === id;
});
if(movieIndex !== -1) {
this.movieList[movieIndex].title = movie.title;
this.movieList[movieIndex].year = movie.year;
return 1;
}else {
return 0;
}
}
}
};
The names of the functions are the same as the traditional operations with Mongoose. The first property is a list to store all the movies in the collection.
id
, here is where crypto
comes to help.id
is passed, then it returns the specific movie, all the list otherwise..filter
method of JavaScript arrays does pretty much what we need, remove and return the found movie.It is very likely some of you may find better ways to implement the operations above, or even point out that I should return a shallow copy of the list instead of the property itself. Because this is just a simple demo to fake CRUD operations, I would suggest you customize my object for your needs or even connect to a real database.
In /api/controllers
create movie.js
and paste the following code:
'use strict';
// Include our "db"
var db = require('../../config/db')();
// Exports all the functions to perform on the db
module.exports = {getAll, save, getOne, update, delMovie};
// GET /movie operationId
function getAll(req, res, next) {
res.json({ movies: db.find()});
}
// POST /movie operationId
function save(req, res, next) {
res.json({success: db.save(req.body), description: "Movie added to the list!"});
}
// GET /movie/{id} operationId
function getOne(req, res, next) {
var id = req.swagger.params.id.value; // req.swagger contains the path parameters
var movie = db.find(id);
if(movie) {
res.json(movie);
}else {
res.status(204).send();
}
}
// PUT /movie/{id} operationId
function update(req, res, next) {
var id = req.swagger.params.id.value; // req.swagger contains the path parameters
var movie = req.body;
if(db.update(id, movie)){
res.json({success: 1, description: "Movie updated!"});
}else{
res.status(204).send();
}
}
// DELETE /movie/{id} operationId
function delMovie(req, res, next) {
var id = req.swagger.params.id.value; // req.swagger contains the path parameters
if(db.remove(id)){
res.json({success: 1, description: "Movie deleted!"});
}else{
res.status(204).send();
}
}
We first included our db and then exported all the functions we need to use as operationId
and handled the business logic.
Notice that in the case that the id
belongs to a movie, getOne
, update
, and delMovie
return the same formatted JSON. We defined the same schema in the Swagger editor earlier.
On the other hand, in case there is no movie with the id of the request, we send a status of no content "204"
.
Now rerun the project without the -m
flag and in the editor, we have to add the operationId
to each route, right after the “verb”:
#in /movie
get:
operationId: getAll
#in /movie
post:
operationId: save
#in /movie/{id}
get:
operationId: getOne
#in /movie/{id}
put:
operationId: update
#in /movie/{id}
get:
operationId: delMovie
The same as before, add a movie:
Result:
After we added more movies, let’s try to retrieve them all by a GET request to /movie
:
Side note, a true Star Wars fan must have noticed that year
of “The Empire Strikes Back” is wrong, it was released in 1980. In addition, I forgot an “s” in the title.
Good, time to update the movie!
Add the id
as a parameter in the PUT request to /movie/{id}
:
Result:
Then, let’s double-check the movie is really updated.
We need a GET request to /movie/{id} with the movie id:
Result:
It works!
Let’s first get all the movies with a GET request to /movie:
We decide to delete “The force awakens”, so let’s send a DELETE request to /movie/{id}
with the right id
:
Result:
Finally, let’s double-check whether it was really deleted or not by sending a GET request to /movie/{id}
:
The NOCONTENT
response confirms it all worked.
In this tutorial, we have seen how Swagger helps to develop a robust RESTful API while at the same time providing elegant documentation that can boost up cooperation among developers (for example backend and frontend developers).
We covered all the basic requests, GET, POST, DELETE, UPDATE to manage a movie collection. At the same time, we practiced with parameters by handling them from the body or the path.
The same can be done to handle parameters from the header of course, so I have a challenge for you:
On Scotch.io there is a cool tutorial to authenticate a Node.js API with JSON web tokens. Why don’t you try to rewrite with swagger to practice with header parameters?
Last but not the least, I remind you again to take a look a the documentation on the official website for a deeper understanding of Swagger.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!