Generating API Document In Nancy

Introduce

In this article, we will learn how to generate the API document in Nancy.

Background

Nowadays, separation of front-end and back-end is the most popular development mode. Not only do websites use this mode but the apps also. Because of the separation, communication may be a big problem when we begin to develop.

As for me, the back-end developers can spend some time to write a document of the APIs that can help the front-end developers to understand what the API is supposed to do.

Now, let's begin to write a API document in Nancy step by step.

Step 1

Before we write the document, we must have some APIs. 

Here, we are creating a new module class. This class contains the basic operations of CRUD.

Look at the following class definition.

  1. public class ProductsModule : NancyModule  
  2. {  
  3.     public ProductsModule() : base("/products")  
  4.     {  
  5.         Get("/", _ =>  
  6.         {  
  7.             return Response.AsText("product list");  
  8.         }, null"GetProductList");  
  9.   
  10.         Get("/{productid}", _ =>  
  11.         {  
  12.             return Response.AsText(_.productid as string);  
  13.         }, null"GetProductByProductId");  
  14.   
  15.         Post("/", _ =>  
  16.         {  
  17.             return Response.AsText("Add product");  
  18.         }, null"AddProduct");  
  19.   
  20.         Put("/{productid}", _ =>  
  21.         {  
  22.             return Response.AsText("Update product");  
  23.         }, null"UpdateProductByProductId");  
  24.   
  25.         Delete("/{productid}", _ =>  
  26.         {  
  27.             return Response.AsText("Delete product");  
  28.         }, null"DeleteProductByProductId");  
  29.    }  
  30. }   

Note

You may find some difference on the usage. We often use only two parameters on GET/POST/PUT/DELETE, but you will find four parameters above. This is because we need the fourth parameter, name of the route, when we generate the document.

Step 2

We already have some APIs. Now, we can generate the document.

Let's now create a new module class named DocModule; we will generate the document in this module.

Here is an example code:

  1. public class DocMudule : NancyModule  
  2. {  
  3.     private IRouteCacheProvider _routeCacheProvider;  
  4.   
  5.     public DocMudule(IRouteCacheProvider routeCacheProvider) : base("/docs")  
  6.     {  
  7.         this._routeCacheProvider = routeCacheProvider;  
  8.   
  9.         Get("/", _ =>  
  10.         {  
  11.             var routeDescriptionList = _routeCacheProvider  
  12.                                          .GetCache()  
  13.                                          .SelectMany(x => x.Value)  
  14.                                          .Select(x => x.Item2)  
  15.                                          .Where(x => !string.IsNullOrWhiteSpace(x.Name))  
  16.                                          .ToList();  
  17.   
  18.             return Response.AsJson(routeDescriptionList);  
  19.         });  
  20.     }  
  21. }   

It's very easy! The interface IRouteCacheProvider helps us to do the job.

Note

I have filtered some routes where the name of route is either null or empty, such as the routes in DocModule class. This happens because the routes in DocModule are not part of our APIs.

For demonstration, I let the code return a JSON object at first. We need to know the result of the expression which can help us to complete the document.

When running the code, here is the screenhost of the result page.

API document in Nancy

But, there is not too much infomation of the APIs. We need to do more things so that we can make the document richer.

Step 3

We can find out that the metadata is empty in the above result. But actually, metadata is a way of making our document richer.

The Metadata is an instance of the RouteMetadata, and here is the definition of it.

  1. public class RouteMetadata  
  2. {  
  3.     //Creates a new instance of the Nancy.Routing.RouteMetadata class.  
  4.     public RouteMetadata(IDictionary<Type, object> metadata);  
  5.   
  6.     //Gets the raw metadata System.Collections.Generic.IDictionary`2.  
  7.     public IDictionary<Type, object> Raw { get; }  
  8.   
  9.     //Gets a boolean that indicates if the specific type of metadata is stored.  
  10.     public bool Has<TMetadata>();  
  11.       
  12.     //Retrieves metadata of the provided type.  
  13.     public TMetadata Retrieve<TMetadata>();  
  14. }   

As you can see, the raw property is the part of the above JSON result. It means, we should create a new type/class to get what we need.

Define a custom Metadata class at first. This class contains some additional infomation about the route, such as the group. I use the group to distinct the different APIs.

  1. public class CustomRouteMetadata  
  2. {  
  3.     // group by the module  
  4.     public string Group { get; set; }  
  5.       
  6.     // description of the api  
  7.     public string Description { get; set; }  
  8.       
  9.     // path of the api  
  10.     public string Path { get; set; }  
  11.       
  12.     // http method of the api  
  13.     public string Method { get; set; }  
  14.       
  15.     // name of the api  
  16.     public string Name { get; set; }  
  17.       
  18.     // segments of the api  
  19.     public IEnumerable<string> Segments { get; set; }  
  20. }   

And, we need to install the package Nancy.Metadata.Modules in our project so that we can define what we need to show in the document.

Creating a new MetadataModule class named ProductsMetadataModule

  1. public class ProductsMetadataModule : MetadataModule<CustomRouteMetadata>  
  2. {  
  3.     public ProductsMetadataModule()  
  4.     {  
  5.         Describe["GetProductList"] = desc =>  
  6.         {  
  7.             return new CustomRouteMetadata  
  8.             {  
  9.                 Group = "Products",  
  10.                 Description = "Get All Products from Database",  
  11.                 Path = desc.Path,  
  12.                 Method = desc.Method,  
  13.                 Name = desc.Name,  
  14.                 Segments = desc.Segments  
  15.             };  
  16.         };  
  17.   
  18.         Describe["GetProductByProductId"] = desc =>  
  19.         {  
  20.             return new CustomRouteMetadata  
  21.             {  
  22.                 Group = "Products",  
  23.                 Description = "Get a Product by product id",  
  24.                 Path = desc.Path,  
  25.                 Method = desc.Method,  
  26.                 Name = desc.Name,  
  27.                 Segments = desc.Segments  
  28.             };  
  29.         };  
  30.   
  31.         //others..  
  32.    }  
  33. }   

After adding the description of the APIs , here is the result.

API document in Nancy

It makes the document richer but it also makes some infomation duplicated! We need to make it easier because what we need is the content under the node value.

Step 4

The interface ICacheRoute has an extension method named RetrieveMetadata which can help us to simplify the result of the document.

Go back to the DocModule, and edit the code as follows.

  1. var routeDescriptionList = _routeCacheProvider  
  2.                             .GetCache()  
  3.                             .RetrieveMetadata<CustomRouteMetadata>()  
  4.                             .Where(x => x != null);   

Now, the result is what we need!

API document in Nancy

As a document, we will not show only a JSON object to others. We should add an HTML page to make it easy to understand.

Step 5

Add an HTML page named doc.html to our project. Here is the content of the page.

  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <meta charset="utf-8" />  
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />  
  6.     <title>Nancy Api Demo</title>  
  7.     <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css" />
  8. </head>
  9. <body>  
  10.     <div class="container body-content">  
  11.         @Each  
  12.         <div class="col-md-6 ">  
  13.             <div class="panel panel-default">  
  14.                 <div class="panel-body">  
  15.                     <p>Group:<strong>@Current.Group</strong></p>  
  16.                     <p>Name:<strong>@Current.Name</strong></p>  
  17.                     <p>Method:<strong>@Current.Method</strong></p>  
  18.                     <p>Path:<strong>@Current.Path</strong></p>  
  19.                     <p>Description:<strong>@Current.Description</strong></p>  
  20.                 </div>  
  21.             </div>              
  22.         </div>          
  23.         @EndEach  
  24.         <hr />  
  25.         <footer>  
  26.             <p>© 2017 - CatcherWong</p>  
  27.         </footer>  
  28.     </div>  
  29.   
  30.     <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"></script>  
  31.     <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"></script>  
  32. </body>  
  33. </html>   

Running the project, you may get the result as follows.

API document in Nancy

What's next?

Swagger is a very popular API Framework! Next time, I will introduce how to use swagger in Nancy by using the open source project Nancy.Swagger.

Thanks for your patient reading!!