Skip to content

Commit

Permalink
feat: add API gateway service
Browse files Browse the repository at this point in the history
- Add API gateway service.
- Update Dockerfiles accordingly, optimizing the build.
- Add `docker-compose.prod.yml` for building the version for production, with optimized steps and smaller footprint.
- Update `docker-compose.yml`.
  • Loading branch information
ckng0221 authored Jan 5, 2024
2 parents 8b995d5 + cebdaf4 commit b7ba8b6
Show file tree
Hide file tree
Showing 32 changed files with 1,369 additions and 92 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/apigateway-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: ApiGateway-CI

on:
push:
branches: ['*']
paths:
- 'apps/apigateway/**'
- '.github/workflows/apigateway-ci.yml'

env:
SERVICE: apigateway

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run lint:apigateway
- run: npm run build:apigateway
- run: npm run test:apigateway
48 changes: 48 additions & 0 deletions apps/Dockerfile.apigateway
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
FROM node:alpine As development

ARG service

WORKDIR /usr/src/app

COPY package*.json ./

WORKDIR /usr/src/app/apps/${service}

COPY apps/${service}/package*.json ./

WORKDIR /usr/src/app

RUN npm install --workspace=${service}

COPY . .

RUN npm run build:${service}


FROM node:alpine as production

ARG service
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package*.json ./

WORKDIR /usr/src/app/apps/${service}

COPY apps/${service}/package*.json ./

WORKDIR /usr/src/app

RUN npm install --omit=dev --workspace=${service}
# RUN echo ${service}

# COPY . .

COPY --from=development /usr/src/app/dist/apps/${service} ./dist

CMD ["node", "dist/server.js"]

# docker build --build-arg="service=apigateway" -f ./apps/Dockerfile.apigateway . -t libraryapp-apigateway
# docker run -p 8080:8080 libraryapp/apigateway
25 changes: 21 additions & 4 deletions apps/Dockerfile.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,42 @@ WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install
WORKDIR /usr/src/app/apps/${service}

COPY apps/${service}/package*.json ./

WORKDIR /usr/src/app

RUN npm install --workspace=${service}

COPY . .

RUN npm run build ${service}

FROM node:alpine as production

ARG service
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install --omit=dev
WORKDIR /usr/src/app/apps/${service}

COPY apps/${service}/package*.json ./

WORKDIR /usr/src/app

RUN npm install --omit=dev --workspace=${service}
# RUN echo ${service}

# COPY . .

COPY --from=development /usr/src/app/dist ./dist
COPY --from=development /usr/src/app/dist/apps/${service} ./dist

CMD ["node", "dist/main.js"]

CMD ["node", "dist/apps/${service}/main"]
# docker build --build-arg="service=apigateway" -f ./apps/Dockerfile.apigateway . -t libraryapp-apigateway
# docker run -p 8080:8080 libraryapp/apigateway
25 changes: 21 additions & 4 deletions apps/Dockerfile.view
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ WORKDIR /usr/src/app

COPY package*.json ./

WORKDIR /usr/src/app/apps/ui
WORKDIR /usr/src/app/apps/${service}
COPY apps/${service}/package*.json ./

WORKDIR /usr/src/app/apps/ui
COPY apps/ui/package*.json ./

WORKDIR /usr/src/app/packages/common
COPY packages/common/package*.json ./

WORKDIR /usr/src/app

RUN npm install
RUN npm install --workspace=${service}
RUN npm install --workspace=@repo/common
RUN npm install --workspace=ui

COPY . .

Expand All @@ -22,17 +29,27 @@ RUN npm run build:ui

FROM node:alpine as production

ARG service
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
ENV SERVICE ${service}

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install --omit=dev
WORKDIR /usr/src/app/apps/${service}

COPY apps/${service}/package*.json ./

WORKDIR /usr/src/app

RUN npm install --omit=dev --workspace=${service}

# COPY . .

# NOTE: keep the app folder name, as will be serving dist/ui/index.html
COPY --from=development /usr/src/app/dist ./dist

CMD ["node", "dist/apps/${service}/main"]
# Execute from this workdir, as shared for both ui and view
CMD node dist/apps/$SERVICE/main
4 changes: 4 additions & 0 deletions apps/apigateway/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BASEURL_BOOK=http://localhost:8001
BASEURL_CUSTOMER=http://localhost:8002
BASEURL_BORROWING=http://localhost:8003
BASEURL_PAYMENT=http://localhost:8004
5 changes: 5 additions & 0 deletions apps/apigateway/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
31 changes: 31 additions & 0 deletions apps/apigateway/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "apigateway",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "tsc",
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
"start": "cd ../.. && cd dist/apps/apigateway && node server.js",
"start:prod": "npm run start",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --fix",
"test": "jest test/*.ts"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"http-proxy-middleware": "^2.0.6",
"morgan": "^1.10.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/cors": "^2.8.17",
"@types/morgan": "^1.9.9",
"jest": "^29.7.0",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1",
"ts-node-dev": "^2.0.0",
"turbo": "^1.11.2",
"typescript": "^5.3.3"
}
}
24 changes: 24 additions & 0 deletions apps/apigateway/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as cors from 'cors';
import 'dotenv/config';
import * as express from 'express';
import { setupLogging } from './logging';
import { setupProxies } from './proxy';
import { ROUTES } from './routes';

const app = express();

const corsOptions: cors.CorsOptions = {
// origin: '*',
// credentials: true, //access-control-allow-credentials:true
// optionsSuccessStatus: 200,
};
app.use(cors(corsOptions));

setupProxies(app, ROUTES);
setupLogging(app);

app.get('/', (req, res) => {
return res.send('Welcome to API Gateway!');
});

export default app;
8 changes: 8 additions & 0 deletions apps/apigateway/src/logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as morgan from 'morgan';
import * as express from 'express';

const loggingMode = process.env.NODE_ENV === 'production' ? 'combined' : 'dev';

export const setupLogging = (app: express.Application) => {
app.use(morgan(loggingMode));
};
9 changes: 9 additions & 0 deletions apps/apigateway/src/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import type { IRoute } from './routes';

export const setupProxies = (app: express.Application, routes: IRoute[]) => {
routes.forEach((r) => {
app.use(r.url, createProxyMiddleware(r.proxy));
});
};
104 changes: 104 additions & 0 deletions apps/apigateway/src/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'dotenv/config';
import { Options } from 'http-proxy-middleware';

export interface IRoute {
url: string;
auth?: boolean;
creditCheck?: boolean;
rateLimit?: { windowMs: number; max: number };
proxy: Options;
}

const BASEURL_BOOK = process.env.BASEURL_BOOK || 'http://localhost:8001';
const BASEURL_CUSTOMER =
process.env.BASEURL_CUSTOMER || 'http://localhost:8002';
const BASEURL_BORROWING =
process.env.BASEURL_BORROWING || 'http://localhost:8003';
const BASEURL_PAYMENT = process.env.BASEURL_PAYMENT || 'http://localhost:8004';

export const ROUTES: IRoute[] = [
// Book
{
url: '/api/book',
auth: false,
creditCheck: false,
proxy: {
target: BASEURL_BOOK,
changeOrigin: true,
pathRewrite: {
'^/api/book': '',
},
},
},
// Customer
{
url: '/api/customer',
auth: false,
creditCheck: false,
proxy: {
target: BASEURL_CUSTOMER,
changeOrigin: true,
pathRewrite: {
'^/api/customer': '',
},
},
},
// Borrowing
{
url: '/api/borrowing',
auth: false,
creditCheck: false,
proxy: {
target: BASEURL_BORROWING,
changeOrigin: true,
pathRewrite: {
'^/api/borrowing': '',
},
},
},
// Paymenet
{
url: '/api/payment',
auth: false,
creditCheck: false,
proxy: {
target: BASEURL_PAYMENT,
changeOrigin: true,
ws: false, // use http polling
pathRewrite: {
'^/api/payment': '',
},
},
},
];

// export const ROUTES: IRoute[] = [
// {
// url: '/free',
// auth: false,
// creditCheck: false,
// rateLimit: {
// windowMs: 15 * 60 * 1000,
// max: 5,
// },
// proxy: {
// target: 'https://www.google.com',
// changeOrigin: true,
// pathRewrite: {
// [`^/free`]: '',
// },
// },
// },
// {
// url: '/premium',
// auth: true,
// creditCheck: true,
// proxy: {
// target: 'https://www.google.com',
// changeOrigin: true,
// pathRewrite: {
// [`^/premium`]: '',
// },
// },
// },
// ];
7 changes: 7 additions & 0 deletions apps/apigateway/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import app from './app';

const PORT = 8080;

app.listen(PORT, () => {
console.log(`Listening at PORT ${PORT}`);
});
13 changes: 13 additions & 0 deletions apps/apigateway/test/app.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, test } from '@jest/globals';
import * as request from 'supertest';
import app from '../src/app';

describe('Test the root path', () => {
test('Should have success status code', () => {
return request(app)
.get('/')
.then((response) => {
expect(response.statusCode).toBe(200);
});
});
});
Loading

0 comments on commit b7ba8b6

Please sign in to comment.