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
.
Create a routers
folder to keep our files organised
Create a new file/routers/emoji
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
$ 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" });
});
});
});
We need a way to bootstrap our app (set containers, connect to the database, ...) and create an express app.
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:
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