3 ways to mock an API in JavaScript | Where those can help you

3 ways to mock an API in JavaScript | Where those can help you

Let's build a quick workflow and get feedback using a backend-less frontend

πŸ‡§πŸ‡· VersΓ£o PortuguΓͺs Brasil

In this article, you'll see three ways to implement fake API for backend-less frontend application. The focus here is how and not exactly why you should be doing this. Regardless your choice is VueJs, Angular or React, having a backend-less help you building your frontend quickly or perhaps get feedback before implementing the real backend API.

This approach doesn't mean you won't need a backend, instead it intend to help you and your team to be more productive by not depending on the backend, it aims to promote an emergent development, decentralized and collaborative way. In the end it will help the backend implementation.

Thanks to Tony LΓ’mpada who I was lucky enough to work with and also influenced me tons about this topic, I still keep creating API mock in my projects. I recommend check his post out here .

Problems when there is NO API Mock

  • Mainly when we have frontend and backend teams separated, it's quite normal to be blocked, in another words, be waiting some changes in the API backend in order to finish certain work in the frontend.
  • In the best scenarios, the frontend team could say the task is almost done, however, we're waiting for the API backend to finish the tests
  • After the backend API is finished, it's high chances to have rework in the API due to complete the frontend feature. In another words, most the time when the frontend is working in the task, an attribute could be missing or in a wrong format. It's possible that even the api endpoint must be changed in order to follow some UX requirement, performance and so on.

When to use

This post presents how to implement API Mock linked to a respective scenarios. All three scenarios presented has the main goal which is to minimize the problems presented above, that means, create value and get feedback cycles faster.

  • The first scenarios is when you just need a straight forward RESTful API for a given entity. For example, you have a model such as Task or Product and need the CRUD endpoints. For this case, we have a simple library that allow us build a complete API in a easy way.

  • At least for me, most the time the scenarios above is unlikely, at least a login, an endpoint that saves multiple entities or perhaps the output is something outside the create, read, update or delete. In this way, we need a minimum logic before return the data. For this case, using a pure javascript to return objects regarding the business rules.

  • The last scenarios is likely the second one, however, related to the context of the API Design First approach, in another words, we need an API contract that guides the API development, indeed, this contract (YAML) is the source of truth for both frontend and backend. This contract idea by itself is a topic for another post.

1) The well known and awesome json-server

If what you need is a fake API for CRUD, I believe json-server is an awesome solution. It's as simple as install and run a single command line for serving a JSON file as a complete API:

npm install -g json-server

json-server --watch db.json

And bellow a example how the db.json JSON must be structured

# db.json
{
  "posts": [
      {
         "id": 1, 
         "title": "The json-server is awesome", 
         "author": "typicode"
      }, { 
         "id": 2, 
         "title": "Another post", 
         "author": "typicode" 
       }
  ]
}

In this way, we have a RESTFul api for Posts:

# Get the list
GET http://localhost:3000/posts

# Get the post details filtered by the id=1
GET http://localhost:3000/posts/1

# Creates a new post by sending 
# {"title": "New post", "author": "test"} in the body
POST http://localhost:3000/posts

Another interesting feature about json-server, it goes beyond the CRUD, there is a quite rich API and it is possible to filter, change the order by and also pagination.

# List using filter by title and author
GET /posts?title=json&author=typicode

# List posts by getting the page 2 only (max 200 items per page)
GET /posts?_page=2&_limit=200

# Order by
GET /posts?_sort=title&_order=desc

So, basically running one command we can have a great API for any JSON file. The problem with this default usage is that I might need to do extra things, such as a prefix or do some condition based on the request. So, my suggestion is creating a structure and use json-server as module and not as command line directly.

# node ApiMock/server OR npm run mock

ApiMock
β”œβ”€β”€ db.json
└── server
    └── index.js

create the apiMock/server/index.js which is the entry point and the place for business rules when necessary.

# apiMock/server/index.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('ApiMock/db.json')
const middlewares = jsonServer.defaults()
const PORT = process.env.API_MOCK_PORT || 5001

server.use(middlewares)

server.use(
  jsonServer.rewriter({
    '/api/*': '/$1',
  })
)

server.use(router)

server.listen(PORT, () => {
  console.log(`πŸš€ JSON Server is running on http://localhost:${PORT}`)
})

The json file is the same format as presented previously, however, it goes inside the ApiMock folder

# ApiMock/db.json
{
  "posts": [
      {
         "id": 1, 
         "title": "The json-server is awesome", 
         "author": "typicode"
      }, { 
         "id": 2, 
         "title": "Another post", 
         "author": "typicode" 
       }
  ]
}

It's also interesting make it easier to run the api mock by adding the script below in the package.json, in this way, just using the npm run mock will run it

# package.json
{
   ...
  "scripts": {
    "mock": "node ApiMock/server"
    ...
  },

Note that, the request now accepts the /api prefix

GET http://localhost:3000/api/posts 
GET http://localhost:3000/api/posts/1

This is how I recommend using json-server for faking the API, more details and how to improve your mock api can be found directly in the json-server github

If you rather prefer, there is a complete example using this way in the repository below:

2) Using a plain javascript in order to provide the mock data.

This way is based on the structure found on the project D-JΓ  vue (created by Tony LΓ’mpada), which has a VueJS frontend and a Django Backend API, I do recommend checking it out, all links can be found in the end of this post. The big difference here comparing within the first way is, rather than having another application running the API copying the backend, this way will use a folder structure to provide the mock api and a variable API_MOCK to set whether api's client is pointing to the real one or the apimock structure.

Start by creating a db file returning a javascript object

# db_todos.js
export const todos = {
  todos: [
    {description: 'Do the laundry', done: true},
    {description: 'Walk the dog', done: false}
  ]
}

Then, a wrapper to return a promise in the same way the api real does.

# mockutils.js
export function mockasync (data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(data), 600)
  })
}

export function mockasyncerror () {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('Something is wrong')), 600)
  })
}

Then, the main file that has the same api signatures that the real api, however, returning the data files (db_something.js) using the mockasync. Its provide a quite flexible way for returning those data, it can be complete, partials or perhaps simulate error or even connection latency.

# index.js

import {todos} from './db_todos'
import {mockasync} from './mockutils'

const keepLoggedIn = true

export default {
  settings () {
    return mockasync({
      SENTRY_DSN_FRONT: ''
    })
  },
  list_todos () {
    return mockasync(todos)
  },
  add_todo (newtask) {
    return mockasync({description: newtask, done: false})
  },
}

Lastly, in somewhere in your project settings you must create an alias to point whether to the mock api or the real one. For example, the code below depends on an environment variable (API_MOCK) which will define the api or apimock folder.

const _apimock = process.env.API_MOCK === '1' || (process.env.API_MOCK === undefined && _isdev)
const _apijs = _apimock ? 'apimock' : 'api'

The working example can be found here:

3) API Design First Approach

This lastly way seems to be more complicated or perhaps excessive, however, firstly it's simpler than it seems and also organized in responsibilities (router, controllers and data) and then is on top of API Design First Approach which is a topic for another post, however, it promotes a collaborative environment for creating the API using a contract (OAS3), in another words, further on promote collaboration and decentralization, it has a YAML contract file that can be read both for humans and machines. I'll also give some links about this topic. In practice, creating an API MOCK is add or change the route in the YAML and implement the router and its responses following the contract rules. The bonus here is that we won validation and the documentation (swagger) created automatically. Then this contract is exactly the same needed by the backend real API.

Structure

api
└── mock
    └── server
        β”œβ”€β”€ config.js
        β”œβ”€β”€ controllers             πŸ‘ˆ 3) Fake logic to return data
        β”‚   └── news.js
        β”œβ”€β”€ data                    πŸ‘ˆ 4) Mock data
        β”‚   β”œβ”€β”€ index.js
        β”‚   └── news.json
        β”œβ”€β”€ datahelper.js
        β”œβ”€β”€ index.js                πŸ‘ˆ 2) Routers and initialization
        └── openapi.yaml            πŸ‘ˆ 1) Contract (docs and validate the mock api)

The OpenAPI contract (OAS3) where the routers are defined and get the implementation validated

# openapi.yaml
openapi: 3.0.2

info:
  version: 0.0.1
  title: Hackernews Mock API
  description: Mock API

paths:
  /api/news:
    get:
      operationId: hackernews.api.news.list_news
      summary: Returns the latest news
      responses:
        200:
          description: The lastest news
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                      example: 42
                    title:
                      type: string
                      example: Python is a great option for backend and APIs
                    description:
                      type: string
                      nullable: true
                    created_at:
                      type: string
                      format: date-time
                      example: 2021-01-11T11:32:28Z

The entry point which put everything together and define the routers and its respective controller is the index.js which uses express as base.

# server/index.js
const express = require('express')
const swaggerUi = require('swagger-ui-express')
const YAML = require('yamljs')
const cors = require('cors')
const logger = require('morgan')
const bodyParser = require('body-parser')
const { OpenApiValidator } = require('express-openapi-validator')

const config = require('./config')
const swaggerDocument = YAML.load(config.OPENAPI_SPEC)

const news = require('./controllers/news')     πŸ‘ˆ Link to item 3)

const app = express()

app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.text())
app.use(bodyParser.json())

app.use(logger('dev'))
app.use(cors({ credentials: true, origin: config.ORIGIN_URL }))
app.use('/api/docs/', swaggerUi.serve, swaggerUi.setup(swaggerDocument))

new OpenApiValidator({
  apiSpec: config.OPENAPI_SPEC,
  validateRequests: true,
  validateResponses: false,
})
  .install(app)
  .then(() => {
    app.get('/api/news', news.listNews)    πŸ‘ˆ Router from the contract

    http: app.listen(config.PORT, () => {
      console.log(
        'πŸ†™ JSON Server is running on port: ' + config.PORT
      )
    })
  })


# config.js (change parameters)
module.exports = {
  PORT: process.env.API_PORT || 5001,
  ORIGIN_URL: process.env.ORIGIN_URL || '*',
  OPENAPI_SPEC: 'api/mock/server/openapi.yaml',
}

The routers defined in the index.js is related to a controller method, for example, the route /api/news calls the news.listNews. The controllers syntax follows the express and it can return a data or some error based on the request.

# controllers/news.js
const data = require('../data')

module.exports = {
  listNews: (req, res) => {
    const result = data.news
    res.send({ result })
  },
}

Lastly, the data and a helper class in order to convert javascript in to JSON

# data/index.js
const datahelper = require('../datahelper')

module.exports = {
  news: datahelper.parseJson('./data/news.json'),
}

# data/news.json
[
  {
    "id": 1,
    "title": "Confraria DevPro faz setup de projeto VueJS",
    "author": "huogerac",
    "reactions": 42,
    "comments": [],
    "date": "2021-10-06 21:42:26"
  },
  {
    "id": 2,
    "title": "Hashnode is awesome",
    "author": "vyrotek",
    "reactions": 149,
    "comments": [],
    "date": "2021-10-05 21:32:23"
  }
]

# datahelpers.js
const fs = require('fs');
const path = require('path');

module.exports = {
  parseJson: (jsonPath) => {
    return JSON.parse(
      fs.readFileSync(path.resolve(__dirname, jsonPath), 'utf8')
    );
  }
};

The api mock can be served using the command node api/mock/server (it also can be added to the package.json) and the swagger documentation can be accessed at http://localhost:5001/api/docs

The working example can be found here:

I hope this can give you insights and untie your development workflow in those contexts.

Comments and reactions are welcome and the fuel for me and others to continue creating contents πŸ‘

Valeu!

Links:

Β