Skip to content

Commit 65b125b

Browse files
Merge pull request #3 from LuckyOkoedion/develop
Develop
2 parents 7259b23 + 0a611ca commit 65b125b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+9339
-905
lines changed

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

.eslintrc.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
parserOptions: {
4+
project: 'tsconfig.json',
5+
sourceType: 'module',
6+
},
7+
plugins: ['@typescript-eslint/eslint-plugin'],
8+
extends: [
9+
'eslint:recommended',
10+
'plugin:@typescript-eslint/recommended',
11+
'prettier',
12+
],
13+
root: true,
14+
env: {
15+
node: true,
16+
}
17+
};

.gitignore

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
1-
node_modules
2-
# Keep environment variables out of version control
1+
lib-cov
2+
*.seed
3+
*.log
4+
*.csv
5+
*.dat
6+
*.out
7+
*.pid
8+
*.gz
9+
*.swp
10+
11+
pids
12+
logs
13+
results
14+
tmp
15+
16+
# Build
17+
public/css/main.css
18+
19+
# Coverage reports
20+
coverage
21+
22+
# API keys and secrets
323
.env
24+
25+
# Dependency directory
26+
node_modules
27+
bower_components
28+
29+
# Editors
30+
.idea
31+
.husky
32+
*.iml
33+
34+
# OS metadata
35+
.DS_Store
36+
Thumbs.db
37+
38+
# Ignore built ts files
39+
dist/**/*
40+
41+
# ignore yarn.lock
42+
yarn.lock

.prettierrc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
trailingComma: "all",
3+
tabWidth: 2,
4+
singleQuote: true,
5+
};

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: npm run start

README.md

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,66 @@
11
# rest-api-with-hapi-typescript-tsoa-prisma-and-postgresql
22

3-
This is a church office management REST api built with Hapi js, TypeScript, Prisma ORM and Postgresql. With Unit tests and End-toEnd tests.
3+
## Description
4+
5+
There are not many working examples out there on how to use one of the alternatives to express js called 'Hapi Js' with typescript, and do an effective setup for complex projects.
6+
7+
This example is a church office management REST api built with Hapi js, Typescript , Prisma ORM and Postgresql. It is an example of how to structure a hapi js REST Api project into models, routes, controllers and services for effective separation of concerns and unit testing.
8+
9+
Unlike its unfortunately or fortunately more popular rival - express js, Hapi provides many tools out of the box. Enabling you to do session management, security, connectivity, and testing without installing many extra packages or middlewares. And yet these built in tools are extensible.
10+
11+
Prisma ORM is a typescript ORM that helps with database migration, etc.
12+
13+
## Documentation link for reference and demo
14+
15+
[LINK TO DOCUMENTATION](https://church-management-api.herokuapp.com/documentation)
16+
17+
## SIDE NOTE
18+
19+
Jest module mock seem not to be working AS EXPECTED when using hapi js built in inject method for http tests. Though it uses the mock implementation provided, it still validates inputs against dependencies in the mocked classes / services / functions. Alternative is to use chaihttp as used in the e2e tests till I figure out how to mock dependencies with Hapi inject without such behaviour. Though that behaviour is an advantage in some other scenarios, but not for what I needed in the tests written.
20+
21+
## TODO
22+
23+
Handle all errors instead of simply logging to the console. For now, you will see the logs when you run the tests.
24+
25+
## Installation
26+
27+
```bash
28+
$ npm install
29+
```
30+
31+
## Run Database Migration with Prisma ORM
32+
33+
This will automatically create the relevant tables in the database
34+
35+
```bash
36+
$ npm run migrate-db
37+
```
38+
39+
## Build the app to compile typscript
40+
41+
This will also create the prisma client required for performing database operations in the app
42+
43+
```bash
44+
$ npm run build
45+
```
46+
47+
## Start application / server
48+
49+
```bash
50+
$ npm run start
51+
```
52+
53+
## Start development server with auto reload
54+
55+
```bash
56+
$ npm run dev
57+
```
58+
59+
## Run automated tests
60+
61+
Test without watching
62+
63+
```bash
64+
$ npm run test
65+
```
466

5-
It is an example of how to modularize / structure hapi API implementation into controllers and services. Good separation of concerns for ease of maintenance for large projects, and type safety, courtesy of typescript.

dist/app.js

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,52 +18,96 @@ var __importStar = (this && this.__importStar) || function (mod) {
1818
__setModuleDefault(result, mod);
1919
return result;
2020
};
21+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23+
return new (P || (P = Promise))(function (resolve, reject) {
24+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27+
step((generator = generator.apply(thisArg, _arguments || [])).next());
28+
});
29+
};
2130
var __importDefault = (this && this.__importDefault) || function (mod) {
2231
return (mod && mod.__esModule) ? mod : { "default": mod };
2332
};
2433
Object.defineProperty(exports, "__esModule", { value: true });
25-
exports.appInstance = void 0;
2634
const Hapi = __importStar(require("@hapi/hapi"));
2735
const attendanceSumRoutes_1 = __importDefault(require("./attendance-sum/attendanceSumRoutes"));
2836
const attendanceRoutes_1 = __importDefault(require("./attendance/attendanceRoutes"));
37+
const baseRoute_1 = __importDefault(require("./baseRoute"));
2938
const inventoryRoutes_1 = __importDefault(require("./inventory/inventoryRoutes"));
3039
const offeringRecordsRoutes_1 = __importDefault(require("./offering-records/offeringRecordsRoutes"));
3140
const teachingsRoutes_1 = __importDefault(require("./teachings/teachingsRoutes"));
3241
const titheRecordsRoutes_1 = __importDefault(require("./tithe-records/titheRecordsRoutes"));
3342
const usersRoutes_1 = __importDefault(require("./users/usersRoutes"));
43+
const HapiSwagger = __importStar(require("hapi-swagger"));
44+
const Inert = __importStar(require("@hapi/inert"));
45+
const Vision = __importStar(require("@hapi/vision"));
3446
class App {
3547
constructor() {
3648
}
3749
// function to initialize the server after routes have been registered
3850
init() {
39-
// set up server
40-
this.theApp = Hapi.server({
41-
port: process.env.PORT || 3000,
42-
host: process.env.HOST || '0.0.0.0',
43-
});
44-
// register routes
45-
this.theApp.register([
46-
attendanceRoutes_1.default,
47-
attendanceSumRoutes_1.default,
48-
inventoryRoutes_1.default,
49-
offeringRecordsRoutes_1.default,
50-
teachingsRoutes_1.default,
51-
titheRecordsRoutes_1.default,
52-
usersRoutes_1.default
53-
]).then(() => {
54-
console.log("Route(s) have been registered");
55-
});
56-
// initialize app with routes
57-
this.theApp.initialize().then(() => {
58-
console.log("The app has been initialized");
51+
return __awaiter(this, void 0, void 0, function* () {
52+
// set up server
53+
this.theApp = Hapi.server({
54+
port: process.env.PORT || 3000,
55+
host: process.env.HOST || '0.0.0.0',
56+
});
57+
// Configure swagger documentation
58+
const swaggerOptions = {
59+
info: {
60+
title: "Church Office Management REST API Documentation",
61+
version: "1.0.0",
62+
description: "There are not many working examples out there on how to use one of the alternatives to express js called 'Hapi Js' with typescript and effective setup for complex projects. This is a church office management REST api built with Hapi js, Typescript , Prisma ORM and Postgresql. It is an example of how to structure a hapi js REST Api project into models, routes, controllers and services for effective separation of concerns and unit testing.",
63+
contact: {
64+
name: "Lucky Okoedion",
65+
url: "https://www.linkedin.com/in/lucky-okoedion-28b7286a/"
66+
}
67+
}
68+
};
69+
const swaggerPlugins = [
70+
{
71+
plugin: Inert
72+
},
73+
{
74+
plugin: Vision
75+
},
76+
{
77+
plugin: HapiSwagger,
78+
options: swaggerOptions
79+
}
80+
];
81+
// register swagger plugins
82+
yield this.theApp.register(swaggerPlugins, { once: true });
83+
// register routes
84+
yield this.theApp.register([
85+
baseRoute_1.default,
86+
attendanceRoutes_1.default,
87+
attendanceSumRoutes_1.default,
88+
inventoryRoutes_1.default,
89+
offeringRecordsRoutes_1.default,
90+
teachingsRoutes_1.default,
91+
titheRecordsRoutes_1.default,
92+
usersRoutes_1.default
93+
], { once: true }).then(() => __awaiter(this, void 0, void 0, function* () {
94+
var _a;
95+
console.log("Route(s) have been registered");
96+
// initialize app with routes
97+
yield ((_a = this.theApp) === null || _a === void 0 ? void 0 : _a.initialize().then(() => {
98+
console.log("The app has been initialized");
99+
}));
100+
}));
59101
});
60102
}
61103
// Function to start the server for the main application or for tests
62104
start() {
63-
this.theApp.start().then(() => {
64-
console.log(`Server running at: ${this.theApp.info.uri}`);
105+
var _a;
106+
return __awaiter(this, void 0, void 0, function* () {
107+
yield ((_a = this.theApp) === null || _a === void 0 ? void 0 : _a.start());
65108
});
66109
}
67110
}
68111
// create singleton for use in main app or for tests.
69-
exports.appInstance = new App();
112+
const appInstance = new App();
113+
exports.default = appInstance;

dist/attendance-sum/attendanceSumRoutes.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,42 @@ const attendanceSumRoutes = {
2020
{
2121
method: 'POST',
2222
path: '/attendance-sum',
23-
handler: controller.create
23+
handler: controller.create,
24+
options: {
25+
tags: ['api']
26+
}
2427
},
2528
{
2629
method: 'GET',
2730
path: '/attendance-sum',
28-
handler: controller.getAll
31+
handler: controller.getAll,
32+
options: {
33+
tags: ['api']
34+
}
2935
},
3036
{
3137
method: 'GET',
3238
path: '/attendance-sum/{id}',
33-
handler: controller.getById
39+
handler: controller.getById,
40+
options: {
41+
tags: ['api']
42+
}
3443
},
3544
{
3645
method: 'PUT',
3746
path: '/attendance-sum/{id}',
38-
handler: controller.update
47+
handler: controller.update,
48+
options: {
49+
tags: ['api']
50+
}
3951
},
4052
{
4153
method: 'DELETE',
4254
path: '/attendance-sum/{id}',
43-
handler: controller.delete
55+
handler: controller.delete,
56+
options: {
57+
tags: ['api']
58+
}
4459
}
4560
]);
4661
})

dist/attendance/attendanceController.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
1414
Object.defineProperty(exports, "__esModule", { value: true });
1515
exports.AttendanceController = void 0;
1616
const attendanceService_1 = require("./attendanceService");
17-
const boom_1 = __importDefault(require("boom"));
17+
const boom_1 = __importDefault(require("@hapi/boom"));
1818
class AttendanceController {
1919
create(request, h) {
2020
return __awaiter(this, void 0, void 0, function* () {

dist/attendance/attendanceRoutes.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,48 @@ const attendanceController_1 = require("./attendanceController");
1414
const controller = new attendanceController_1.AttendanceController();
1515
// configure the routes
1616
const attendanceRoutes = {
17-
name: "attendance",
17+
name: "theAttendance",
1818
register: (server) => __awaiter(void 0, void 0, void 0, function* () {
1919
server.route([
2020
{
2121
method: 'POST',
2222
path: '/attendance',
23-
handler: controller.create
23+
handler: controller.create,
24+
options: {
25+
tags: ['api']
26+
}
2427
},
2528
{
2629
method: 'GET',
2730
path: '/attendance',
28-
handler: controller.getAll
31+
handler: controller.getAll,
32+
options: {
33+
tags: ['api']
34+
}
2935
},
3036
{
3137
method: 'GET',
3238
path: '/attendance/{id}',
33-
handler: controller.getById
39+
handler: controller.getById,
40+
options: {
41+
tags: ['api']
42+
}
3443
},
3544
{
3645
method: 'PUT',
3746
path: '/attendance/{id}',
38-
handler: controller.update
47+
handler: controller.update,
48+
options: {
49+
tags: ['api']
50+
}
3951
},
4052
{
4153
method: 'DELETE',
4254
path: '/attendance/{id}',
43-
handler: controller.delete
55+
handler: controller.delete,
56+
options: {
57+
tags: ['api']
58+
}
4459
}
4560
]);
4661
})

0 commit comments

Comments
 (0)