💻
Building and hosting a WebApp
  • Getting started
  • Project setup
    • Requirements
    • Files organisation
    • Lerna
    • Linter
    • Prettier
    • GitHook
    • Testing
    • Conclusion
  • Backend
    • Files organisation
    • Environment config
    • Express API
    • Security
    • Database
    • GraphQL
    • User authentication
    • Conclusion
  • Frontend
    • Create React App
    • Files organisation
    • Styles
    • Apollo Hooks
    • Form management
    • User authentication
    • Writing tests
    • Types generation
    • Conclusion
  • DevOps
    • CI/CD
    • AWS
      • Managing secrets
      • Pricing
      • RDS
      • S3
      • Route53
      • CloudFront
      • Serverless
      • Security
      • CloudFormation
    • Conclusion
  • 🚧Stripe payment
  • 🚧File upload
Powered by GitBook
On this page

Was this helpful?

  1. Backend

Express API

PreviousEnvironment configNextSecurity

Last updated 5 years ago

Was this helpful?

is a minimal and flexible Node.js web application framework. We will be using it to build our API.

Let's create an endpoint to get an emoji.

Here's the list of what we have to do:

  • DeleteEmoji.ts and Emoji.spec.ts.

  • Install express

  • Create a routers folder to keep our files organised

  • Create a new file/routers/emoji

  • Create a new file app.ts

  • Create a new file index.ts (our main file)

  • Update our config with shared.ts and .env to add port.

  • To simplify imports, addindex.ts exporting all the relevant files to each folder.

We will create the following file structure

.
├── app.ts
├── index.ts
└── routers
    ├── Emoji
    |   ├── index.spec.ts
    |   └── index.ts
    └── index.ts
~/packages/api
$ yarn add express @types/express

To keep things clean, each top-level endpoint will have a router. The router defines the GET, POST, PUT and DELETE.

import { Router, Request, Response } from "express";
import { getFetch } from "../../containers";

export class EmojiRouter {
  public readonly router = Router();
  private readonly _fetch = getFetch();
  constructor() {
    this.router.get("/:emoji", this.get);
  }
  get = async (request: Request, response: Response) => {
    const { emoji } = request.params;
    const jsonResponse = await this._fetch("https://api.github.com/emojis");
    const data = await jsonResponse.json();
    if (!data[emoji]) {
      return response.status(404).json({ error: "emoji not found" });
    }
    return response.json({ emoji: data[emoji] });
  };
}
export * from "./emoji";

We can test any method by mocking request and response using jest.fn().mockReturnValue().

routers/emoji/index.spect.ts
import fetch from "node-fetch";

import { EmojiRouter } from "./index";
import { setFetch } from "../../containers";
import { Request, Response } from "express";

describe("routes/emoji", () => {
  describe("/get/:emoji", () => {
    it("should fetch and return emoji passed as a param", async () => {
      // setup everything we need
      const computer = "mocked url";
      const data = { computer };
      const json = jest.fn().mockResolvedValue(data);
      const fetchMock = jest.fn(() => ({ json }));
      setFetch((fetchMock as unknown) as typeof fetch);

      const router = new EmojiRouter();

      // execute the code we want to test
      const request = ({ params: { emoji: "computer" } } as unknown) as Request;
      const response = ({} as unknown) as Response;
      response.json = jest.fn().mockReturnValue(response);
      await router.get(request, response);

      // assert the result
      expect(response.json).toHaveBeenCalledWith({ emoji: computer });
    });
    it("should return an error if not found", async () => {
      // setup everything we need
      const computer = "mocked url";
      const data = { computer };
      const json = jest.fn().mockResolvedValue(data);
      const fetchMock = jest.fn(() => ({ json }));
      setFetch((fetchMock as unknown) as typeof fetch);

      const router = new EmojiRouter();

      // execute the code we want to test
      const request = ({
        params: { emoji: "inexistent" }
      } as unknown) as Request;
      const response = ({} as unknown) as Response;
      response.status = jest.fn().mockReturnValue(response);
      response.json = jest.fn().mockReturnValue(response);
      await router.get(request, response);

      // assert the result
      expect(response.status).toHaveBeenCalledWith(404);
      expect(response.json).toHaveBeenCalledWith({ error: "emoji not found" });
    });
  });
});

When types don't match, we use as unknown first to force TypeScript to accept the other type. This is useful when we are mocking params.

We need a way to bootstrap our app (set containers, connect to the database, ...) and create an express app.

app.ts
import * as express from "express";
import fetch from "node-fetch";
import { setFetch } from "./containers";
import { EmojiRouter } from "./routers";

export const bootstrap = async () => {
  setFetch(fetch);
  // connect to db here later...
};

export const createApp = () => {
  const app = express();
  app.use("/emoji", new EmojiRouter().router);
  return { app };
};

Our main file requires it and starts the server:

index.ts
require("dotenv").config();

import { bootstrap, createApp } from "./app";
import { config } from "./config";

(async () => {
  await bootstrap();
  const { app } = createApp();
  app.listen(config.port, () => {
    console.info("Server listing on port " + config.port);
  });
})();

Note: sharedConfig and .env needs to be updated to add port.

export interface SharedConfig {
  logLevel: string;
  port: number;
}

export const sharedConfig = {
  logLevel: process.env.LOG_LEVEL || "info", // we will come back to logs later
  port: parseInt(process.env.PORT || "1234")
};
CONFIG_ENV=local
LOG_LEVEL=debug
PORT=3000

branch available on GitHub.

Express
express