Different Problem, One Solution : API Gateways
Tags: api, javascript, node, tutorial
Published: 2024-07-10 21:33:58

Greetings fellow developers, both experienced and newcomers.

Have you ever found yourself dealing with various APIs featuring different endpoints, leading to confusion about which one the frontend should access? Perhaps you even accidentally exposed all endpoint responses to the frontend.

There exists a solution to this issue: the API Gateway.

## What is API Gateway?

API Gateway is a service that helps manage and secure APIs (Application Programming Interfaces). It acts as a front door for the APIs, routing requests to the appropriate backend services, handling API versioning, security, and access control, and also providing features like rate limiting and caching. In simpler terms, API Gateway facilitates the communication and interaction between different software applications.


![API Gateway diagram from Medium](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o13l89vgeenulrsa7ufm.png)


Lets say you have a mobile e-commerce app with various features like user authentication, product search, and ordering. To manage the APIs for this app, you can use Amazon API Gateway. With API Gateway, you can create different API endpoints for user authentication, product search, and order placement. 

For instance, you can set up an API endpoint `/auth` for user authentication that routes incoming requests to a Lambda function for verifying user credentials. Another endpoint `/products` can handle product search requests by connecting to a separate backend service that retrieves product information from a database.

Additionally, let's implement API versioning by creating different versions of the APIs to support backward compatibility. The API Gateway allows you to easily manage and switch between versions without disrupting the app's functionalities.

Moreover, you can enhance security by setting up API key authentication or integrating with IAM roles to control access to specific APIs. With features like rate limiting, you can prevent abuse of the APIs by restricting the number of requests a user can make within a certain time period.

By utilizing API Gateway in this scenario, you can effectively manage and secure the communication between your mobile e-commerce app and the backend services, ensuring a seamless and protected experience for your users.

Some most used API Gateway examples include AWS API Gateway, Azure API Management, Google Cloud Endpoints, and Kong.

## Work In Progress

After understanding What, let's move on to How.

To begin with, create an empty repository

`mkdir api-gateway`

Next, initialize npm

`npm init`

Once npm is initialized, proceed to install necessary packages for the application

```shell
npm i express nodemon morgan express-rate-limit axios
```

Create an `index.js` file in the repository, and your final result should resemble this.

![server folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jlwxc8uxop2w1c1x8p7t.png)

Ensure making slight alterations within `package.json` file to initiate `nodemon`. Optionally, you may set `"type": "module"` for proceeding with ES6, although our approach will be in CommonJs.

Starting with necessary imports,

```js
const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
```

initializing the app

```js
const app = express();
const PORT = 3000;
```

> _NOTE: Although CORS is not utilized in this example, it is essential to implement it._

Let's set the middleware for Morgan

```js
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);

app.use(morgan('combined'));
```

And for error handlers middleware

```js
const handleError = (err, req, res, next) => {
  console.error(err);
  res.status(500).send('Internal Server Error');
};
app.use(handleError);
```

In this instance, we will utilize the dummy APIs provided by JSONPlaceHolder and ReqRes.

- JSONPlaceHolder
  - `/posts` - Fetch a collection of posts.
  - `/comments` - Access a set of comments.
  - `/albums` - Get a listing of albums.
  - `/photos` - Obtain a series of photos.
  - `/todos` - Get a rundown of todos.
  - `/users` - Retrieve a register of users.

- ReqRes
  - `/login` - Used for requesting login access.

```js
app.use('/api', async (req, res, next) => { ... }
```

inside we'll implement `try` and `catch` method

```js
try{ ... } catch (error) { ... }
```

inside `try` lets start the main work

```js
let response;
const path = req.path;
```

```js
if (
    path.startsWith("/posts") ||
    path.startsWith("/comments") ||
    path.startsWith("/albums") ||
    path.startsWith("/photos") ||
    path.startsWith("/todos") ||
    path.startsWith("/users")
) {
    // Route to JSONPlaceholder
    response = await axios.get(`https://jsonplaceholder.typicode.com${path}`);
} else if (path.startsWith("/reqres-users")) {
    // Route to ReqRes
    response = await axios.get(`https://reqres.in/api${path}`);
} else if (path.startsWith("/reqres-login")) {
    // Handle ReqRes login
    response = await axios.post("https://reqres.in/api/login", {
      email: "eve.holt@reqres.in",
      password: "cityslicka",
  });
} else {
    res.status(404).send("Endpoint not found");
    return;
}

res.json(response.data);
```

And that is it. We're done implementing our first API Gateway.

The overall code should look like this.

```js
const express = require("express");
const axios = require("axios");
const rateLimit = require("express-rate-limit");
const morgan = require("morgan");

const app = express();
const PORT = 3000;


const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
});
app.use(limiter);


app.use(morgan("combined"));

// Error Handling Middleware
const handleError = (err, req, res, next) => {
  console.error(err);
  res.status(500).send("Internal Server Error");
};
app.use(handleError);

// Unified API endpoint
app.use("/api", async (req, res, next) => {
  try {
    let response;
    const path = req.path;

    if (
      path.startsWith("/posts") ||
      path.startsWith("/comments") ||
      path.startsWith("/albums") ||
      path.startsWith("/photos") ||
      path.startsWith("/todos") ||
      path.startsWith("/users")
    ) {
      // Route to JSONPlaceholder
      response = await axios.get(`https://jsonplaceholder.typicode.com${path}`);
    } else if (path.startsWith("/reqres-users")) {
      // Route to ReqRes
      response = await axios.get(`https://reqres.in/api${path}`);
    } else if (path.startsWith("/reqres-login")) {
      // Handle ReqRes login
      response = await axios.post("https://reqres.in/api/login", {
        email: "eve.holt@reqres.in",
        password: "cityslicka",
      });
    } else {
      res.status(404).send("Endpoint not found");
      return;
    }

    res.json(response.data);
  } catch (error) {
    next(error);
  }
});

app.listen(PORT, () => {
  console.log(`API Gateway running on port ${PORT}`);
});

```

## Test and Results

Its time to test our API Endpoint, I'm using Insomnia, but you can use any of your choiceSure, let's go ahead and send a request to the API endpoint to see if everything is working as expected.

![user enpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jpxge9bl298dovy26iyd.png)


![post endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1lcz2sqjupywzmnnqxp7.png)


![todos endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfolkfnbwrvw2arpy08m.png)


![endpoint logs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndtqo61xtf5whlwhlplh.png)



Keep Hacking!