Skip to content

Commit

Permalink
feat: Add healthz endpoints to api and frontend (powerhouse-inc#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
KirillDogadin-std authored Mar 31, 2023
1 parent ca0a7da commit 25baa4e
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 38 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ To understand what is planned you can read and ask questions here:
To install correct node version, we recommend that you use [nvm](https://github.com/nvm-sh/nvm). If you have `nvm` installed you can run `nvm install && nvm use` to automatically use the correct node version. The version is detected from the [.nvmrc](./.nvmrc).

If you do not have a code editor setup, we recommend that you use [Visual Studio Code](https://code.visualstudio.com/) to get started. It is very beginner friendly and you can move on to something else down the road if you want to.

## Health endpoints

Both api and frontend have health endpoints that can be used to check if the service is up and running.

See the respective descriptions in the [api](./api/README.md#health-endpoint) and [frontend](./frontend/README.md#health-endpoint) READMEs.
4 changes: 4 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ npx prisma studio
### Logging configuration

The configuration is received from the `logger.config.ts` file at the root of the project. Adjust the file parameters to control the logger behaviour.

## Health endpoint

Endpoint available at `/healthz` path. Provides response if api is currently running and prisma (orm) is able to execute queries.
126 changes: 98 additions & 28 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"gql-query-builder": "^3.8.0",
"graphql-request": "^5.2.0",
"prisma": "^4.11.0",
"typescript": "^4.9.5"
"typescript": "^4.9.5",
"vitest-mock-extended": "^1.1.3"
}
}
11 changes: 11 additions & 0 deletions api/src/__mocks__/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PrismaClient } from '@prisma/client';
import { beforeEach } from 'vitest';
import { mockDeep, mockReset } from 'vitest-mock-extended';

const prisma = mockDeep<PrismaClient>();

beforeEach(() => {
mockReset(prisma);
});

export default prisma;
35 changes: 29 additions & 6 deletions api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,42 @@ import type { Express } from 'express';
import express from 'express';
import expressPlayground from 'graphql-playground-middleware-express';
import { getChildLogger } from './logger';
import prisma from './database';

const logger = getChildLogger({ msgPrefix: 'APP' });
const startupTime = new Date();

export const createApp = (): Express => {
logger.debug('Creating app');
const app = express();

app.get('/', expressPlayground({
endpoint: '/api/graphql',
settings: {
'editor.theme': 'light',
},
}));
app.get('/healthz', async (_req, res) => {
try {
// TODO: after migration to postgres, do SELECT 1
await prisma.user.findFirst();
} catch (error) {
return res.status(500).json({
status: 'DB failed initialization check',
time: new Date(),
startupTime,
});
}
return res.json({
status: 'healthy',
time: new Date(),
startupTime,
});
});

app.get(
'/',
expressPlayground({
endpoint: '/api/graphql',
settings: {
'editor.theme': 'light',
},
}),
);

return app;
};
28 changes: 27 additions & 1 deletion api/tests/auxilary.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { test, expect } from 'vitest';
import {
test, expect, vi, describe,
} from 'vitest';
import { getJwtSecret, getJwtExpirationPeriod } from '../src/env/getters';
import { restoreEnvAfterEach } from './helpers/env';
import { ctx } from './helpers/server';
import prisma from '../src/__mocks__/database';

restoreEnvAfterEach();

Expand Down Expand Up @@ -34,3 +38,25 @@ test('Env: jwt expiration in seconds format', async () => {
process.env.JWT_EXPIRATION_PERIOD = '3600';
expect(getJwtExpirationPeriod()).toBe('1h');
});

describe('Healthz', () => {
vi.mock('../src/database');

test('healthz: returns 200', async () => {
prisma.user.findFirst.mockResolvedValueOnce({ id: '1', username: 'asdf', password: 'asdf' });
const url = `${ctx.baseUrl}/healthz`;
const res = await fetch(url);
expect(res.status).toBe(200);
const json: any = await res.json();
expect(json.status).toBe('healthy');
});

test('healthz: returns 500', async () => {
prisma.user.findFirst.mockRejectedValueOnce(new Error('test'));
const url = `${ctx.baseUrl}/healthz`;
const res = await fetch(url);
expect(res.status).toBe(500);
const json: any = await res.json();
expect(json.status).toBe('DB failed initialization check');
});
});
9 changes: 7 additions & 2 deletions api/tests/helpers/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { createApp } from '../../src/app';

interface TestContext {
client: GraphQLClient;
baseUrl: string;
}

function getGraphqlTestContext() {
let serverInstance: Server | null = null;
let baseUrl: string | null = null;
return {
async before() {
const app = createApp();
Expand All @@ -19,10 +21,12 @@ function getGraphqlTestContext() {
throw new Error('Unexpected server address format');
}
const { port } = serverAddress;
return new GraphQLClient(`http://0.0.0.0:${port}/graphql`);
baseUrl = `http://0.0.0.0:${port}`;
return { client: new GraphQLClient(`${baseUrl}/graphql`), baseUrl };
},
async after() {
serverInstance?.close();
baseUrl = null;
},
};
}
Expand All @@ -31,8 +35,9 @@ function createTestContext(): TestContext {
const context = {} as TestContext;
const graphqlTestContext = getGraphqlTestContext();
beforeEach(async () => {
const client = await graphqlTestContext.before();
const { client, baseUrl } = await graphqlTestContext.before();
context.client = client;
context.baseUrl = baseUrl;
});
afterEach(async () => {
await graphqlTestContext.after();
Expand Down
4 changes: 4 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ Please refer to the root readme to learn more about general development setup.
## Production

You can build application for production using `npm run build` and then locally preview production build via `npm run preview`.

## Health endpoint

Endpoint available at `/healthz` path. Provides response if frontend is currently running.
9 changes: 9 additions & 0 deletions frontend/server/api/healthz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const startupTime = new Date()

export default eventHandler(() => {
return {
status: 'healthy',
time: new Date(),
startupTime
}
})

0 comments on commit 25baa4e

Please sign in to comment.