神刀安全网

Speed up your RESTful API development in Node.js with Swagger

Overview

So it happened I started this project with 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:

  • Interactive documentation.
  • Discoverability.
  • Client SDK generation.

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 create a RESTful API to manage our movies collection.

You can directly clone the app repository from my Github here .

Prerequisites

  • A basic understanding of node.js, how to install packages etc.
  • CRUD cycle.
  • Yaml syntax .

Let’s move to the installation of Swagger.


Installation and run the example

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 

N.B. Each subfolder contains a README.md that for simplicity I didn’t include above.

  • api : As the name suggests, everything related to the API development, the controllers, mocks, helpers. A special mention goes to the /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.
  • config : It contains 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 guys to 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.
  • test : Your test for controllers and helpers are (guess what!) created here.

Obviously, app.js is the main file which 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 parameter and greets the person. Not original at all, I give you that, but as we are beginners it is good 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:

  • Validation and endpoint routing.
  • Docs on the fly generation.
  • Easy-to-read Yaml.

Speed up your RESTful API development in Node.js with Swagger

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 the list of routes. By clicking on any of them we can have a good understanding of the parameter required, the format of request and responses, more generally a description of the route.

Now, to launch the example, start the app by running

 swagger movie-collection start 

NB whenever we modify a file, it automatically restarts the server (great!)

Then, open a second command line and launch the editor with:

 swagger movie-collection 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 .

Speed up your RESTful API development in Node.js with Swagger

We are asked to enter a paramenter name : Go for it and test the route.

Here is my result:

Speed up your RESTful API development in Node.js with Swagger

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 the example 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 response 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 consume and produce , 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/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 

GET /movie

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 which contain a unique id, a title and the year.

NB swagger editor autosaves whatever we input, which is great for us, but I noticed that sometimes is 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.

Speed up your RESTful API development in Node.js with Swagger

Now test the route and check the result:

Speed up your RESTful API development in Node.js with Swagger

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 a 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.

POST /movie

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 

NB 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:

Speed up your RESTful API development in Node.js with Swagger

Here is the result:

Speed up your RESTful API development in Node.js with Swagger

GET /movie/{id}

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:

Speed up your RESTful API development in Node.js with Swagger

and here is the expected response:

Speed up your RESTful API development in Node.js with Swagger

PUT /movie/{id}

Now it’s time 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:

Speed up your RESTful API development in Node.js with Swagger

And the result:

Speed up your RESTful API development in Node.js with Swagger

DELETE /movie/{id}

The last operation, let’s delete a movie by a given id.

Copy and past 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:

Speed up your RESTful API development in Node.js with Swagger

Here is the result:

Speed up your RESTful API development in Node.js with Swagger

…And congratulations! We finished the design and now it’s about time to write some javascript code.


Implement the controller and run the real app

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 /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.

  • Save : We need to create a unique id, here is where crypto comes to help.
  • Find : If the id is passed, then it returns the specific movie, all the list otherwise.
  • Remove : The .filter method of javascript arrays does pretty much what we need, remove and return the found movie.
  • Update : Update the movie if it exists in the collection.

NB 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.

Movie controller

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

Add a movie

The same as before, add a movie

Speed up your RESTful API development in Node.js with Swagger

Result:

Speed up your RESTful API development in Node.js with Swagger

Get all the movies

After we added more movies, let’s try retrieve them all by a GET request to /movie:

Speed up your RESTful API development in Node.js with Swagger

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 a "s" in the title.

Good, time to update the movie!

Update a movie

Add the id as a parameter in the PUT request to /movie/{id}

Speed up your RESTful API development in Node.js with Swagger

Result:

Speed up your RESTful API development in Node.js with Swagger

Then, let’s doublecheck the movie is really updated

Get a single movie

We need a GET request to /movie/{id} with the movie id:

Speed up your RESTful API development in Node.js with Swagger

Result:

Speed up your RESTful API development in Node.js with Swagger

It works!

Delete a movie

Let’s first get all the movies with a GET request to /movie:

Speed up your RESTful API development in Node.js with Swagger

We decide to delete "The force awakens", so let’s send a DELETE request to /movie/{id} with the right id:

Speed up your RESTful API development in Node.js with Swagger

Result:

Speed up your RESTful API development in Node.js with Swagger

Finally, let’s double check whether it was really deleted or not by sending a GET request to /movie/{id}:

Speed up your RESTful API development in Node.js with Swagger

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 an 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 cooltutorial 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.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Speed up your RESTful API development in Node.js with Swagger

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址