Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documenting and building Node.js APIs on IBM i #41

Open
worksofliam opened this issue Jan 27, 2020 · 0 comments
Open

Documenting and building Node.js APIs on IBM i #41

worksofliam opened this issue Jan 27, 2020 · 0 comments
Labels
nodejs Node.js topics

Comments

@worksofliam
Copy link
Owner

worksofliam commented Jan 27, 2020

In this blog, I will explain my stages for documenting and building APIs in Node.js.

There are three main stages:

  1. Documenting the API
  2. Creating the models / entities
  3. Developing the API

Documenting the API

Documenting the API is almost a requirement today when building APIs.

  • It allows the business to decide what each API should do
  • It defines how the APIs should be organized
  • You're able to share the API specs so others can start to implement calling them before they are live
  • It makes developing the API a ton easier because you know how the API should work

Documenting is quite a hard scenario. You should definitely use OpenAPI 3.0 (or previously known as Swagger). It allows you to define what a RESTful API should do and look like. The problem with OpenAPI 3.0 is that is can be quite daunting if you haven't seen it before:

paths:
  /users:
    get:
      summary: Returns a list of users.
      description: Optional extended description in CommonMark or HTML.
      responses:
        '200':    # status code
          description: A JSON array of user names
          content:
            application/json:
              schema: 
                type: array
                items: 
                  type: string

The above REST API returns a simple JSON array of strings. It is important that before building your API, you define it with the OpenAPI spec. This means you can decide on functionality before implementing so you spend less time re-writing.

The other part is that OpenAPI can also define models, which you can think of re-usable structures in your APIs. For example, let's say we have a User model. The User model contains a name, email and phone number. We might have two APIs:

  • GET /users - which returns an array of our User model
  • GET /users/:id - might return a single User model
paths:
  /users:
    get:
      summary: List of Users
      tags: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
      operationId: get-users
  '/users/{id}':
    parameters:
      - schema:
          type: string
        name: id
        in: path
        required: true
    get:
      summary: Single User by ID
      tags: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
      operationId: get-users-id

components:
  schemas:
    User:
      title: User
      type: object
      properties:
        name:
          type: string
        email:
          type: string
        phone:
          type: string
      description: User structure

Notice in this API spec, our User model lives in the Components property. The problem I have is that I really don't enjoy manually writing YAML. There are lots of tools for building, maintaining and viewing OpenAPI specifications, but my favourite is Stoplight. It was shown to me by my friend Connor and it has changed my life. It makes building APIs fun.

image

Creating the Models / Entities / Using an ORM

Using an ORM, or at least encapsulating your database access and business logic makes writing web APIs a ton easier. It allows you to separate out the web API (Express) logic from any database or business logic. It means that you can also re-use your business logic and database access across multiple APIs instead of writing it over and over again.

There lots of different ORMs and ways to create Models for your data (TypeORM, mongoose, Sequelize). Since I am primarily working with Db2 for i, I have the struggle that Db2 isn't a well-supported database with those ORMs.

Previously I was manually creating classes for all my models and data access. I was getting frustrated with re-writing a lot of the stuff and wanted something to create everything for me (kind of like a traditional ORM would) - so I built db2Model.

db2Model will build classes based on a Db2 for i table for me. It will create

  • each column as a property in the class (with the correct JavaScript types and column documentation)
  • static methods to get a model (row) by primary or unique key(s).
  • instance methods to update and delete the row based on the properties.
  • instance methods to fetch other Models based on foreign keys in the column

Here's an example model for the DEPARTMENT table in the Db2 for i Sample schema.

const db2 = require('../db2');
const GenericTable = require('./GenericTable');
const Employee = require('./Employee');

module.exports = class Department extends GenericTable {
  static _table = 'DEPARTMENT';
  static _schema = 'SAMPLE';
  static _keys = ["DEPTNO"];
  static _columns = ["DEPTNO","DEPTNAME","MGRNO","ADMRDEPT","LOCATION"];

  constructor(row) {
    super();

    /** @type {String} DEPTNO */
    this.deptno = row.DEPTNO.trim();
    /** @type {String} DEPTNAME */
    this.deptname = row.DEPTNAME;
    /** @type {String} MGRNO */
    this.mgrno = (row.MGRNO ? row.MGRNO.trim() : null);
    /** @type {String} ADMRDEPT */
    this.admrdept = row.ADMRDEPT.trim();
    /** @type {String} LOCATION */
    this.location = (row.LOCATION ? row.LOCATION.trim() : null);
  }

  /**
   * Fetchs Employee by mgrno
   * @returns {Employee} Returns new instance of Employee
   */
  async getEmployee() {
    return await Employee.Get(this.mgrno);
  }

  /**
   * Fetchs Department by admrdept
   * @returns {Department} Returns new instance of Department
   */
  async getDepartment() {
    return await Department.Get(this.admrdept);
  }

  Delete() {
    //Code removed to preserve space
  }

  Update() {
    //Code removed to preserve space
  }

  /**
   * Fetchs Department by keys
   * @param {String} deptno
   * @returns {Department} Returns new instance of Department
   */
  static async Get(deptno) {
    const row = await this.Find({deptno}, 1);
    return (row.length === 1 ? row[0] : null);
  }

}

All that code is self-generated from db2Model. Notice, that it extends the GenericTable class. That parent class has two extra methods:

  • Find which allows us to query our table with a where clause and limit
  • Join which allows us to query and join to another table by it's Model too.

It means that in our express APIs we can just inherit and use our Model to do all the work. Of course, we can add more instance methods to our model to do more work too!

app.get('/departments', async (req, res) => {
  const depts = await Department.Find();
  res.json(depts); //Returns array of Department models
});

app.get('/departments/:id', async (req, res) => {
  const deptno = req.params.id;
  const dept = await Department.Get(deptnp);
  res.json(dept); //Returns single Department model
});

app.post('/department/:id', async (req, res) => {
  //Simple update API - should add more validation checks
  const deptno = req.params.id;
  const newLocation = req.body.location; //New location

  try {
    const department = Department.Get(deptno);
    department.location = newLocation;
    department.Update();
    res.status(200).json({
      message: "Update successful."
    });
  } catch (e) {
    res.status(500).json({
      message: "Update failed."
    });
  }
});

Developing the API

I think I've posted enough code examples. The important part about building the API is that your web APIs match your OpenAPI spec.

That's where https://www.npmjs.com/package/express-openapi-validator can help. You can build your OpenAPI specification with Stoplight, define the validator in your express application and link it to your OpenAPI specification which validates all input and output based on your definitions.

await new OpenApiValidator({
  apiSpec: './test/resources/openapi.yaml',
  validateRequests: true, // (default)
  validateResponses: true, // false by default
}).install(app);

The page linked above has a great example of an OpenAPI spec and an express application.

@worksofliam worksofliam added the nodejs Node.js topics label Feb 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nodejs Node.js topics
Projects
None yet
Development

No branches or pull requests

1 participant