Spring MVC and Swagger. Generating documentation for your RESTful web services - PART 2

Automating your RESTful web services documentation Using Spring MVC with Swagger for generating it


Part 2 - Implementing some business logic and configuring Swagger


1.- Configuring swagger and swagger-ui

1.1.- What is swagger ?

Swagger is a specification and complete framework implementation for describing, producing, consuming, and visualizing RESTful web services as stated by their official website

Personally I find Swagger to be a pretty nice way to generate my web services documentation instead of having to rewrite everything again (I already comment my code so it should be enough!)

However at the moment swagger does not support Spring MVC natively but there is an implementation over at github here

As stated by the developer not all the Swagger annotations are currently supported but the implementation works mainly with the Spring MVC annotations(@Controller, @RequestMapping , etc.) which I just what I needed.

1.2.- Integrating swagger into spring

First thing to do is to create a configuration file needed by the swagger-spring-mvc implementation. So go ahead and create a swagger.properties files under src/main/resources/config


#this is the base url for your applications (basically the root url for all your webservices)
documentation.services.basePath=http://localhost:9090/spring-mvc-swagger-tutorial
documentation.services.version=1.0

head up to your spring-web.xml configuration file and add the following lines :


 


    
    
1.3.- Swagger UI

In order to display the web services documentation swaggers it's necessary to download and add the swagger-ui libraries to your project

To my knowledge the Swagger UI dependency cannot be found in the maven repository, so you need to downloaded manually from here :
swagger-ui download

Once you've downloaded it unzip it in your maven webbap folder. you should end up with a tree similar to this one :


Note : It's equally possible to clone the GIT repository instead of downloading the files if you prefer

You will now need to edit the index.html file and change the discoveryUrl parameter to reflect your application

From :
   "discoveryUrl": "http://petstore.swagger.wordnik.com/api/api-docs.json",  
To your application base url (eg : http://localhost:9090/spring-mvc-swagger-tutorial/api-docs.json) :
   "discoveryUrl":"http://localhost:9090/spring-mvc-swagger-tutorial/api-docs.json", 

2.- Writing the business logic

The future controller will be using a simple DAO implementation to recover data from the database

Below is the DAO interface that will be used by the controller. To save some space I will not be posting the DAO implementation here but you can get it from GitHub here

package com.ufasoli.tutorial.swagger.springmvc.core.dao;

import com.ufasoli.tutorial.swagger.springmvc.core.status.OperationResult;
import java.util.List;

public interface DAO<T, U> {

    public OperationResult create(T object);  
    public OperationResult<T> update(U id, T object);  
    public OperationResult delete(U id);  
    public T findOne(U id);  
    public List<T> findAll();
}

I realize the interface and the implementation are far from perfect there is no proper exception handling but since that is not the main goal of this tutorial please bear with me :)

3.- Writing the Spring controllers and swagger "magic"

Finally we are almost there one last thing before we can begin writing some spring controllers and adding some swagger "magic"

There are a few swagger annotations allowing you to customize what it will be printed out by the swagger-ui interface. We'll be using the following :

  • @ApiOperation : describes an API operation or method
  • @ApiParam : describes an api method parameter and it's eventual constraints (ie : required)
  • @ApiError : describes a possible error (HTTP Status) and the error's cause

Bellow is a sample spring controller annotated with the appropriate swagger annotations

package com.ufasoli.tutorial.swagger.springmvc.web.services;
import com.ufasoli.tutorial.swagger.springmvc.core.dao.DAO;
import com.ufasoli.tutorial.swagger.springmvc.core.model.Book;
import com.ufasoli.tutorial.swagger.springmvc.core.status.OperationResult;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;


@Controller
@RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_VALUE)
public class BookService {

    @Autowired
    private DAO<Book, String> bookDAO;


    @ApiOperation(value = "Creates a Book")
    @RequestMapping(method = RequestMethod.POST)
    public  @ResponseBody void create(@ApiParam(required = true, name = "book", value = "The book object that needs to be created")@RequestBody Book book){

        return bookDAO.create(book);
    }

    
    @ApiOperation(value = "Method to update a book")
    @RequestMapping(method = RequestMethod.PUT, value = "/{bookId}")
    public  @ResponseBody OperationResult<Book> update(
            @ApiParam(required = true, value = "The id of the book that should be updated", name = "bookId")@PathVariable("bookId") String bookId,
                    @ApiParam(required = true, name = "book", value = "The book object that needs to be updated")@RequestBody Book book){

        return bookDAO.update(bookId, book);
    }

    
    @RequestMapping(method = RequestMethod.GET)
    @ApiOperation(value = "Lists all the books in the database")
    public  @ResponseBody List<Book> list(){
        return bookDAO.findAll();
    }

    
    @ApiOperation(value = "Retrieves a book based on their id")
    @ApiErrors(value = {@ApiError(code=404, reason = "No book corresponding to the id was found")})
    @RequestMapping(method = RequestMethod.GET, value = "/{bookId}")
    public  @ResponseBody Book view(@ApiParam(name = "bookId" , required = true, value = "The id of the book that needs to be retrieved")@PathVariable("bookId") String bookId){
         
      Book book =  bookDAO.findOne(bookId);

        if(book == null){

            response.setStatus(404);
        }
        return book;
    }

    
    @ApiOperation(value = "Deletes a book based on their id")
    @RequestMapping(method = RequestMethod.DELETE, value = "/{bookId}")
    public  @ResponseBody OperationResult delete(@ApiParam(name = "bookId", value = "The id of the book to be deleted", required = true)@PathVariable("bookId") String bookId){
      return bookDAO.delete(bookId);

    }

}

4.- Time to check-out the generated documentation

Once again fire up your Jetty server by running the maven goal

  mvn jetty:run

Once your server is started head up to the following URL using your inseparable favorite browser :

http://localhost:9090/spring-mvc-swagger-tutorial/

If Murphy's law has not applied here we should see swagger-ui's homepage

Go ahead and click on the show/hide link right next to the books element. You should see a pretty nice ui show you the different api operations available along with the required params and HTTP methods :

What's even better swagger-ui offers you a light REST client allowing you to directly test your API operations by simply selecting an operation and filling the required parameters.

You can play with the different annotations and options and observe the different results that they produce

5.- Wrapping things up

So that's it, I hope this tutorial was useful to you, even though you will no longer have any excuse to not having nice up-to-date documentation for your RESTful applications.

You can find this sample application over at github :

Swagger - Spring MVC tutorial sample application code

16 comments:

  1. Hi,

    After updating the root-context.xml file. I got an error

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'documentationController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.List com.mangofactory.swagger.spring.controller.DocumentationController.handlerMappings; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    PS:I am new to java

    ReplyDelete
  2. Hi,
    What version of Spring, Java, Swagger are you using?
    Did you start a new project or cloned mine from GitHub?.

    ReplyDelete
    Replies
    1. Hi,
      It worked.But i am very curios how do you manage to generate the optional information of the Book Model.
      Java 1.7
      Spring 3.1.0
      Swagger 0.6.5

      Delete
    2. Hi, Glad to hear you sorted things out.
      What do you mean by optional information?

      Delete
    3. I meant like in the Model schema of the Book, with each attribute along with its type, whether it mandate parameter or not i.e. optional true or false is also shown.
      I also used the same annotations as shown above.

      Delete
    4. That's weird perhaps a problem with your version ( I use an older version)
      Also are you controller methods annotated with @ResponseBody? otherwise you will be returning a ModelAndView instead of a Book object

      Delete
    5. Yes they are annotated with @ResposeBody ofcourse.

      Delete
  3. Really very useful article. But I'm struggling with documenting header, could you please help?

    ReplyDelete
  4. Hi, sorry for the late response I was on vacations, what issue are you having?

    ReplyDelete
  5. Hi Ulises Fasoli and Pranav!

    How to upgrade your project swagger version from 1.0 to 1.2?

    References : https://github.com/martypitt/swagger-springmvc/issues/197

    Thank you very much!

    ReplyDelete
  6. I am not happy with Swagge as we need to include the swagger annotations (@Api, ... )in code, is there any other way of doing it other than using swagger annotations in code.

    Regards,
    Rajagopal Yendluri(Raj)

    ReplyDelete
    Replies
    1. Hello,
      As far as I know it's not possible to have your documentation generated without using the Swagger annotations (unless you write your own scanner).
      An alternative is to use RESTdoclet http://ig-group.github.io/RESTdoclet/ which understand Spring MVC annotations out of the box.

      Delete
  7. Hi Ulises Fasoli,
    I have copied Swagger UI dist contents to webapp/resources/swagger-UI/ path and I am using swagger 0.6.3 version.Now I get the following error"Uncaught exception pointing swagger.js and it says response.api.length is the culprit. Can you help with this issue,

    ReplyDelete
  8. Hi from different comments I can see that apparently the configuration for the version I was using 0.5.x is not compatible to 0.6.x.
    I'm not currently using this new version of the Swagger library, but I'll try to update the github project this weekend and see if I can reproduce your problem.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Hello Ulises,

    When I run the application using http://localhost:8080/spring-mvc-swagger-tutorial/

    I'm getting following error. Could anyone please help ?
    Can't read from server. It may not have the appropriate access-control-origin settings.

    ReplyDelete