Product
Enterprise
Solutions
DocumentationPricing
Resources
Book a DemoSign InGet Started
Product
Solutions
Solutions
Blog |Building a Node API with API-first design

Building a Node API with API-first design

API Governance  |  Jul 28, 2022  |  9 min read

Building a Node API with API-first design image

    What is API-first design and how do you approach it as a Node developer?

    Now that APIs are a key aspect of the software industry, the concept of API-first design is gaining traction.

    Under this paradigm, an API definition is created and collaborated on before any code is written.

    This enables stakeholders to ensure the API meets their objectives as well as
    streamline the development phase once the API design is finalized.

    API-first design requires new tools, like the OpenAPI 3 standard, as well as new ways of working and new best practices.

    In this article, we’ll look at API-first design from a developer’s perspective. We’ll get a better understanding of API-first design principles and see what tools support API-first design in the Node.js ecosystem.

    Why API-first design?

    It used to be that an API was added to a project as an afterthought. In the era of the API economy, however, interconnected APIs are the backbone of many applications.

    It’s therefore important for organizations to ensure there is sufficient planning and
    collaboration with stakeholders on the design of an API.

    Under the API-first design paradigm, stakeholders from across an organization will collaborate on a central API definition before the development phase begins.
    Some of the advantages of this approach include:

    • Improved collaboration.

    APIs now need to meet the demands of complex data and integrations, while also being efficient across devices, platforms, and operating systems. API-first design allows multiple stakeholders to provide feedback on the API to ensure it fulfills these key purposes.

    • Parallel development.

    Once the development phase begins, development teams can work in parallel with the confidence that they’re working on a settled API contract.

    For example, one team could work on implementing the API in code while another can be working on creating mocks and tests, while yet another can work on client applications.

    • Automated code generation.

    By using a spec like OpenAPI 3, developers can utilize powerful scaffolding tools that allow the creation of code, tests, and documentation through automated generator software.

    OpenAPI 3

    To allow collaboration on an API under the API-first paradigm, stakeholders will work together on a document that defines the API (often called a “contract”).

    The most popular spec for doing this is OpenAPI 3. This is an open-source format
    designed to describe RESTful APIs.

    It’s easy to write and easy for stakeholders to provide feedback on. Here’s a trivial example of an OpenAPI 3 definition written with YAML (though JSON
    can also be used).

    openapi: 3.0.0
    info:
    	version: 1.0.0
    	title: Petstore
    	description: A sample API to illustrate OpenAPI concepts
    paths:
    	/pets:
    	  get:
    		description: Returns all pets
    		responses:
    		  '200':
    			description: ok

    // petstore.yml

    💡

    Note: While it’s possible to write definitions by hand, it’s easier to
    work in visual environments like OpenAPI-GUI.

    An important thing to notice from this example is that the definition includes meta-information about the API (like the name and description) as well as the technical specification of API paths and their responses.

    Path example

    Let’s now take a look at how we’d add a more complex path GET /pets/{id} to an
    OpenAPI 3 definition.

    Some of the noteworthy properties include:

    • operationId is an optional unique string used to identify an operation. As you’ll see below, code generators use this value to name corresponding methods.

    • parameters defines input parameters. In this case, a dynamic path parameter.

    • responses defines the API response. In this case, we’ve shown the 200 responseand the returned properties. In a real example, we’d likely include error responses as well.

    paths:
    	/pets/{id}:
    	  get:
    		description: Get a specific pet
    		operationId: getPetById
    		parameters:
    		  - name: id
    			in: path
    			required: true
    			schema:
    			type: integer
    		responses:
    		  '200':
    			description: ok
    			content:
    			  application/json:
    			    schema:
    				  type: object
    				  properties:
    				    name:
    					  type: string
    					id:
    					  type: integer
    					type:
    					  type: string

    Development phase

    Once the stakeholders have finalized the API design, developers will begin
    implementing it in code. This not only includes the API application, but also tests, mocks, and documentation.

    Of course, an OpenAPI 3 definition can be implemented in any web-facing programming language like Python, Ruby, or C#. In this article, though, we’ll be providing examples using Node.js and Express.

    Let’s begin by defining a basic Express server module to which we’ll add our OpenAPI routes to shortly.

    const express = require('express')
    const app = express()
    app.use(express.json())
    // API goes here
    module.exports = app

    app.js

    OpenAPI Backend

    One of the less-obvious advantages of API-first design is that it provides opportunities for automated code scaffolding as the definition can serve as a blueprint for API code and tests.

    OpenAPI Backend is one such tool that allows for scaffolding routes in an OpenAPIbased app.

    Let’s use it in our demo project. Firstly, we load the Open API 3 definition (assuming it’s a YAML file called petstore.yml), and pass it to the OpenAPI Backend constructor.

    Next, we register a controller module that will handle the scaffolded routes. We’ll look at this more in the next section.

    We then initialize the backend and load the API as Express middleware.

    const OpenAPIBackend = require('openapi-backend').default
    // 1. Create API with definition file
    const api = new OpenAPIBackend({ definition: './petstore.yml' })
    // 2. Register controllers
    api.register(require('./controllers.js'))
    // 3. Initialize backend
    api.init()
    // 4. Load as express middleware
    app.use((req, res) => api.handleRequest(req, req, res))

    Here’s the full Express app. Note that this app will now have routes and validated
    requests corresponding to the OpenAPI definition saving developers from manually coding these.

    const express = require('express')
    const OpenAPIBackend = require('openapi-backend').default
    const app = express()
    app.use(express.json())
    const api = new OpenAPIBackend({ definition: './petstore.yml' })
    api.register(require('./controllers.js'))
    api.init()
    app.use((req, res) => api.handleRequest(req, req, res))
    module.exports = app

    app.js

    Controllers

    How do we hook into our OpenAPI routes with controller methods?

    You may recall that the GET /pets/{id} path we defined in the OpenAPI definition had an operationId property of getPetById.

    Code generators like OpenAPI Backend uses this ID as a key for assigning a handler
    method for the routes it defines. More specifically, defining a method getPetById will provide us with a callback for the GET /pets/{id} path.

    As with any Express controller, you would typically use this callback to unpack the context,req, and res objects and pass off to business logic and the data layer.

    💡

    You can read more on this in the article How to structure an Express.js REST API with best practices.

    Unlike regular Express callbacks, however, OpenAPI Backend handlers are passed a special Context object as the first argument, which contains the parsed request, the matched API operation, and input validation results.

    module.exports = {
      getPets: (context, req, res) => {
    	console.log(context.operation)
        /*
    	 {
    	  method: get,
    	  path: '/pets',
    	  operationId: 'getPets'
    	  summary: 'Returns all pets'
    	  ...
    	  }
        */
        console.log(context.validation)
        /*
        {
    	  valid: true,
    	  errors: null
         }
         */
         
    	// Business logic
    	const pets = await DB.getPets()
    ...
     
    	res.json(pets)
       },
       getPetById: (context, req, res) => {
    ...
       }
      }

    // controllers.js

    Testing

    We can also use our OpenAPI definition to power API tests. The OpenAPIValidators library, for example, provides plugins like jest-openapi for the Jest JavaScript test runner.

    Let’s see how we’d use jest-openapi to test the Express app we defined above. For this we’ll use Jest and Supertest to make test HTTP calls to our app.

    jest-openapi example

    In our test file, we first require Supertest and the Express app module. We can then import jest-openapi and our OpenAPI definition.

    Let’s now write a test case for the GET /pets/{id} endpoint where we’ll verify that the response satisfies the OpenAPI spec.

    First, we get an HTTP response by using the get method of the Supertest request API.

    Next, we make an assertion expect(res).toSatisfyApiSpec(). The toSatisfyApiSpec assertion is provided by the jest-openapi plugin and ensures an HTTP response satisfies the provided OpenAPI definition.

    // 1. Require supertest and server app
    const request = require('supertest')
    const app = require('./app.js')
     
    // 2. Import Jest OpenAPI plugin
    import jestOpenAPI from 'jest-openapi'
     
    // 3. Load OpenAPI definition
    jestOpenAPI('./petstore.yml');
     
    // 4. Test
    describe('GET /pets/{id}', () => {
      it('should satisfy OpenAPI spec', async () => {
      
        // 5. Get an HTTP response from supertest
        const res = await request(app).get('/pets/1')
       
       // 6. Assert that the HTTP response satisfies the OpenAPI spec
       expect(res).toSatisfyApiSpec();
      });
    });

    Now we can run this test from command line:

    $ npx jest app.test.js

    API monitoring with Treblle

    If APIs are crucial to your organization, it’s important to implement an API observability tool into your workflow.

    This will allow you to learn what’s happening from within your API so that root cause analysis can be performed quickly when problems occur.

    Treblle is API observability software that can be installed in an Express app with just a few lines of code. It will monitor and trace any issues with your API so that they can be caught and fixed before affecting your uses.

    In addition to API observation, Treblle offers your API a variety of other useful features including:

    • Auto-generated API documentation
    • API quality scores
    • 1 click testing

    💡

    Treblle is free for up 30,000 requests and can be installed in minutes, so give it a try!

    Wrap up

    It used to be that organizations added APIs to a app as an afterthought. Now that APIs are a key driver of applications, the concept of API-first design is growing in popularity.

    Under this new paradigm, an API definition is created using specs like OpenAPI 3.
    Stakeholders will then collaborate on the API before any code is written to ensure it meets organizational objectives.

    The API-first design paradigm can result in a variety of benefits including improved collaboration between teams, parallel development, and automated code generation.

    Related Articles

    How to Design Clean and Consistent API Endpoints coverAPI Governance

    How to Design Clean and Consistent API Endpoints

    Inconsistent API endpoints slow development, confuse teams, and frustrate users. This guide breaks down the principles and best practices for designing clean, predictable, and scalable API paths that improve developer experience and reduce errors.

    Introducing the All-New Treblle Go SDK: A Rewrite to Support Treblle V3 coverAPI Governance

    Introducing the All-New Treblle Go SDK: A Rewrite to Support Treblle V3

    We’ve updated the Treblle Go SDK to support Treblle V3, with a full rewrite focused on performance, ease of use, and better integration with Go tools and frameworks. This guide walks you through the changes, setup, and new features.

    Learn Concurrency in Go by Throwing a Party coverAPI Governance

    Learn Concurrency in Go by Throwing a Party

    In this blog, we explore how Go’s concurrency model helps us do more with less. Through a fun kitchen party analogy, you’ll learn about goroutines, channels, waitgroups, semaphores, and how to use them together to build efficient, concurrent programs.

    © 2025 Treblle. All Rights Reserved.
    GDPR BadgeSOC2 BadgeISO BadgeHIPAA Badge