DEV Community

Cover image for Auto-Generate an API Server Without AI
Nolan Miller
Nolan Miller

Posted on • Edited on

Auto-Generate an API Server Without AI

To connect my front end interface to the database, I need to design an API to allow the frontend application to retrieve and send data. This will enable my user logic, like creating accounts, signing in and out, etc. It will enable all the logic around roasts.

I've created an Express API before, but this time I wanted to see if I could learn a bit more about Swagger in the build process.

The Swagger Suite

Swagger is a company that designed and maintains three products, the Swagger Editor, Swagger CodeGen and Swagger UI. These three products work hand in hand to make creating an OpenAPI compliant application (and the documentation) easier!

Using a Contract

The process of using Swagger to create an API starts with the Swagger Editor. This tool allows you to create what is called a contract. The contract is a YAML document that defines different things about the application like the name, routes that it will handle and so forth.

YAML vs. JSON

If you haven't worked with YAML before, it's a looser object-based markup language that has some similarity with JSON, but it's much easier to type up quickly. Here's an example of the same content in JSON and then in YAML:

// JSON Example
{
  "name": "ThisApp",
  "description": "An example of data.",
  "list": [
    "option1",
    "option2",
    "option3"
  ],
  "object": {
    "key": "value"
  }
}
Enter fullscreen mode Exit fullscreen mode
# YAML Example
name: ThisApp
description: An example of data.
list:
  - option1
  - option2
  - option3
object:
  key: value
Enter fullscreen mode Exit fullscreen mode

Notice that YAML, while still machine-readable, is a bit easier for humans to read too, making it a great markup language for this specification.

Defining API Specs

Using the Swagger Editor and following OAS, you can write out everything that you would ordinarily program into an API.

At the top-level, you will define the specifics of the application:

openapi: 3.0.3
info:
  title: Roast - Know Your Home Roasts
  description: # This will appear in the API UI
  version: 1.0.0
servers:
  - url: # A url to one or more servers here
tags:  These are groups of paths
  - name: user
    description: Operations for your user
  - name: roast
    description: Access to roasts
paths:
  # Define all of your paths in this object
components:
  # Define reusable pieces of code here
Enter fullscreen mode Exit fullscreen mode

The magic of the CodeEditor comes to life when you begin defining paths. Take the following path that I defined to get a single roast by id.

# ... contract information
paths:
  # ... users paths, etc.
  /roasts/{roastId}:
    get:
      tags:
        - roast
      summary: Get roast by id
      description: Must include a valid roast id
      parameters:
        - $ref: '#/components/parameters/roastIdParam'
      responses:
        '200': 
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Roast"
        '400':
          $ref: '#/components/responses/Invalid'
        '404':
          $ref: '#/components/responses/NotFound'
Enter fullscreen mode Exit fullscreen mode

Let's break down this object. First, it is called /roasts/{roastId}. This means that we are defining the expected behavior of the server when a request is sent to this route. What happens below that?

  • get: This tells the Swagger tools that we would like to define a GET request endpoint for this path, the rest of the information about the GET request will be inside that object.
  • tags: This is an optional property of an endpoint, but a useful one in the UI, as it allows you to group your endpoints in your documentation.
  • summary: and description: In the on-screen rendering of the Swagger UI, you will see both of these fields in different places. Summary provides a quick explanation of the endpoint's functionality, and the Description provides extra details.
  • parameters: This property of get: is where the path parameters can be specified. You can include information like, whether or not the parameter is required and what type of data to expect.
  • responses: Here is the meat and potatoes. Identified as status code strings, each valid response from an endpoint can be listed here for documentation and code generation purposes.

Staying DRY with Components

Developing a contract like this, you will likely find yourself typing the same things over and over. And you may know as a programmer, that we want to follow the DRY principle: "Don't Repeat Yourself." For example, when defining a required request body, there will be several endpoints that may require the same object.

Well here's where components come in. For this example, you can define a schema and then reference that schema anywhere in the contract using $ref: .

So, at the bottom of my contract, I have a components: object.

components:
  schemas:
    Roast: # An arbitrary name to identify the schema
      type: object
      properties:
        id:
          type: integer
          format: int64
          example: 10
        ## Include all other properties
Enter fullscreen mode Exit fullscreen mode

This component object, contains a schema called Roast , so now, if I needed to specify that this object needed to be sent in a request, say, in a POST request to /roast to add a new roast. I can reference the object this way:

/roast:
  post:
    # additional specifications
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Roast'
Enter fullscreen mode Exit fullscreen mode

You can also do this in defining your parameters and many other repeated sections of your spec!

Automatic Documentation

While you're typing away in your Swagger Editor, all the while, the Swagger UI is being constantly updated in the window to your right. Swagger UI is updating what will become your API documentation! This means you don't have to go back later and write out the documentation on your own.

Swagger Editor and UI

Swagger UI Endpoint

The best part about this, is that it will be served alongside your application (so long as you use CodeGen to create it).

Let Swagger Code Your API Too!

Once you feel like your API is up to spec, you can have a working server in 17 different languages/frameworks in seconds! Just click Generate Server, and select your flavor and CodeGen reads your OAS spec and downloads a server.

In Node, your code comes out in a few different directories.

generated-server/
|-- api/
|-- controllers/
|-- service/
|-- utils/
|-- README.md
|-- index.js
|-- package.json
Enter fullscreen mode Exit fullscreen mode

The api directory contains your OpenAPI spec. The controllers directory contains a file for each of your path groups, with exported functions specifically for handling the unique paths and operations of your application. The service directory, is where you will hook these operations up to your database and perform the business logic of the application, and utils contains functions that help read and write data.

Your server will actually live in index.js.

Is It Really That Easy?

Yes! Well, sort of.

CodeGen does in fact make a working server for you, but it's still up to you to hook up the database and design the logic of the application! But, it gives you a complete and organized skeleton that you can work in!

Here's an example of the code that it output for my POST request to /roasts .

// controllers/Roast.js
// ...
module.exports.addRoast = function addRoast (req, res, next, body) {
  Roast.addRoast(body)
    .then(function (response) {
      utils.writeJson(res, response);
    })
    .catch(function (response) {
      utils.writeJson(res, response);
    });
};

// service/RoastService.js
// ...
exports.addRoast = function(body) {
  return new Promise(function(resolve, reject) {
    var examples = {};
    examples['application/json'] = {
  "notes" : "Doesn't taste as good as last time... I wonder if the weather is making the beans roast faster now that it's warmer",
  "heatLevel" : "Med",
  "origin" : "Ethiopian",
  // ...
  "id" : 10,
};
    if (Object.keys(examples).length > 0) {
      resolve(examples[Object.keys(examples)[0]]);
    } else {
      resolve();
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

The above code was entirely generated by Swagger CodeGen. However, it won't actually add a roast object anywhere. This is creating a mock object and sending it back. But, after hooking up a Postgres database, and creating queries with this logic inside the service, the API will be fully operational!

Would You Use It?

I loved working with Swagger on this API, it was the first time that I committed to learning some of the intricacies of OAS to generate the server. The only rub that I have with it is that the docs are not formatted the best, and they can be hard to navigate searching for what you want to add to the server.

But, once you're familiar with OAS, this tool can save a ton of time. Not only do you have extremely thorough documentation when you're done, but you can treat the generated code as a to-do list of the functions and logic that you need to implement as you build!

Would you give it a try?


Check Out the Project

If you want to keep up with the changes, fork and run locally, or even suggest code changes, here’s a link to the GitHub repo!

https://github.com/nmiller15/roast

The frontend application is currently deployed on Netlify! If you want to mess around with some features and see it in action, view it on a mobile device below.

https://knowyourhomeroast.netlify.app

Note: This deployment has no backend api, so accounts and roasts are not actually saved anywhere between sessions.

Top comments (0)