Skip to content

Commit e61eafa

Browse files
authored
feat: provide example using database with 100% test coverage (#18)
* init example * feat: readme * feat: finish integration test, init docker compose * feat: docker-compose * refactor: runner script * feat: update schema * feat: add logger * feat: using fastify-postgres * refactor: more detailed test * feat: delete try catch, user migration tool * refactor: use return instead of reply
1 parent f2def01 commit e61eafa

14 files changed

+309
-0
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Here a list of the projects with a description, search in this page what you are
1515
| [validation-messages] | `schema` `validation` | How you can customize the error messages of input schema validation |
1616
| [winston-logger] | `logger` | Example how to use winston as a custom logger |
1717
| [typescript decorators] | `typescript` | Example how to use typescript decorators to build application |
18+
| [fastify postgres] | `postgres` `crud` | Simple CRUD app that show how integrate fastify with database, with 100% test coverage |
1819
| [tests] | `tests` | Example of how to test your fastify application |
1920

2021

@@ -44,3 +45,4 @@ Licensed under [MIT](./LICENSE).
4445
[validation-messages]:./validation-messages/
4546
[winston-logger]: ./winston-logger
4647
[typescript decorators]: ./typescript-decorators
48+
[fastify postgres]: /.fastify-postgres

fastify-postgres/Dockerfile

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM node:14-alpine
2+
3+
WORKDIR /src
4+
COPY package.json /
5+
EXPOSE 3000
6+
7+
# on CI normaly you use npm ci / npm clean-install
8+
RUN npm install
9+
COPY . /
10+
CMD ["npm", "start"]
11+
12+

fastify-postgres/README.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Fastify Postgres CRUD
2+
3+
Motivation, this simple app are meant to show on how to use Postgres alongside with Fastify, and to show how to achieve 100% test coverage with it.
4+
5+
## Requirements
6+
7+
- Docker & Docker Compose
8+
- Node.js
9+
10+
## How to run?
11+
12+
With npm
13+
14+
```
15+
$ npm start:db // start postgres db with docker
16+
$ npm install
17+
$ npm start
18+
19+
# testing
20+
$ npm test
21+
```
22+
23+
With docker compose
24+
25+
```
26+
# migration up
27+
$ docker-compose -f docker-compose.yaml -f compose-file/migration-up.yaml --exit-code-from fastify_postgres
28+
29+
# run the app
30+
$ docker-compose up
31+
32+
# testing
33+
$ docker-compose -f docker-compose.yaml -f compose-file/test.yaml --exit-code-from fastify_postgres
34+
35+
# migration down
36+
$ docker-compose -f docker-compose.yaml -f compose-file/migration-up.yaml --exit-code-from fastify_postgres
37+
38+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
version: "3.8"
2+
services:
3+
fastify_postgres:
4+
command: >
5+
sh -c "npm run migration:down"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
version: "3.8"
2+
services:
3+
fastify_postgres:
4+
command: >
5+
sh -c "npm run migration:up"
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
version: "3.8"
2+
services:
3+
fastify_postgres:
4+
command: >
5+
sh -c "npm test"

fastify-postgres/docker-compose.yaml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
version: "3.8"
2+
services:
3+
db:
4+
image: postgres:latest
5+
expose:
6+
- "5432"
7+
ports:
8+
- "5432:5432"
9+
environment:
10+
- POSTGRES_DB=fastify_postgres
11+
- POSTGRES_USER=postgres
12+
- POSTGRES_PASSWORD=postgres
13+
14+
fastify_postgres:
15+
build:
16+
context: ./
17+
dockerfile: Dockerfile
18+
volumes:
19+
- .:/src
20+
command: >
21+
sh -c "npm start"
22+
expose:
23+
- "3000"
24+
ports:
25+
- "3000:3000"
26+
depends_on:
27+
- db
28+
environment:
29+
- DATABASE_URL=postgresql://postgres:postgres@db_postgres:5432/fastify_postgres?schema=public

fastify-postgres/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict";
2+
3+
const app = require("./server")({ logger: true });
4+
5+
const start = async () => {
6+
try {
7+
await app.listen(3000);
8+
} catch (err) {
9+
app.log.error(err);
10+
process.exit(1);
11+
}
12+
};
13+
14+
start();

fastify-postgres/migration.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use strict";
2+
3+
const Postgrator = require("postgrator");
4+
5+
const connectionString =
6+
process.env.DATABASE_URL ||
7+
"postgresql://postgres:postgres@localhost:5432/fastify_postgres?schema=public";
8+
9+
const postgrator = new Postgrator({
10+
connectionString: connectionString,
11+
migrationDirectory: __dirname + "/migrations",
12+
driver: "pg",
13+
});
14+
15+
if (process.env.MIGRATE_ACTION === "do") {
16+
postgrator
17+
.migrate()
18+
.then((appliedMigrations) => console.log(appliedMigrations))
19+
.catch((error) => console.log(error));
20+
}
21+
22+
if (process.env.MIGRATE_ACTION === "undo") {
23+
postgrator
24+
.migrate("000")
25+
.then((appliedMigrations) => console.log(appliedMigrations))
26+
.catch((error) => console.log(error));
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CREATE TABLE IF NOT EXISTS books (
2+
id serial PRIMARY KEY,
3+
title varchar (255) NOT NULL
4+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE IF EXISTS books

fastify-postgres/package.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "fastify-database-example",
3+
"version": "1.0.0",
4+
"description": "Example on how to use, fastify with postgres database unit and integration test",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "tap",
8+
"start": "node index.js",
9+
"start:db": "docker-compose up -d db",
10+
"migrate:up": "MIGRATE_ACTION=do node migration.js",
11+
"migrate:down": "MIGRATE_ACTION=undo node migration.js"
12+
},
13+
"keywords": [
14+
"postgres",
15+
"fastify"
16+
],
17+
"author": "Manda Putra <manda@omg.lol> (https://github.com/mandaputtra)",
18+
"license": "MIT",
19+
"dependencies": {
20+
"fastify": "^3.20.2",
21+
"fastify-postgres": "^3.6.0",
22+
"pg": "^8.7.1"
23+
},
24+
"devDependencies": {
25+
"tap": "^15.0.9"
26+
}
27+
}

fastify-postgres/server.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"use strict";
2+
3+
const fastify = require("fastify");
4+
5+
const connectionString =
6+
process.env.DATABASE_URL ||
7+
"postgresql://postgres:postgres@localhost:5432/fastify_postgres?schema=public";
8+
9+
function build(opts = {}) {
10+
const app = fastify(opts);
11+
12+
app.register(require("fastify-postgres"), { connectionString });
13+
14+
app.get("/", async () => {
15+
const client = await app.pg.connect();
16+
const { rows } = await client.query("SELECT * FROM books");
17+
client.release();
18+
return rows;
19+
});
20+
21+
app.post("/", async (request) => {
22+
const { body } = request;
23+
const client = await app.pg.connect();
24+
const { rows } = await client.query(
25+
"INSERT INTO books (title) VALUES ($1) RETURNING *",
26+
[body.title]
27+
);
28+
client.release();
29+
return rows;
30+
});
31+
32+
app.get("/:id", async (request) => {
33+
const { params } = request;
34+
const client = await app.pg.connect();
35+
const { rows } = await client.query("SELECT * FROM books WHERE id = $1", [
36+
+params.id,
37+
]);
38+
client.release();
39+
return rows;
40+
});
41+
42+
app.patch("/:id", async (request) => {
43+
const { params, body } = request;
44+
return app.pg.transact(async (client) => {
45+
await client.query("UPDATE books SET title = $1 WHERE id = $2", [
46+
body.title,
47+
+params.id,
48+
]);
49+
const { rows } = await client.query("SELECT * FROM books WHERE id = $1", [
50+
+params.id,
51+
]);
52+
return rows;
53+
});
54+
});
55+
56+
app.delete("/:id", async (request) => {
57+
const { params } = request;
58+
const client = await app.pg.connect();
59+
const { rowCount } = await client.query("DELETE FROM books WHERE id = $1", [
60+
+params.id,
61+
]);
62+
client.release();
63+
return { rowCount };
64+
});
65+
66+
return app;
67+
}
68+
69+
module.exports = build;

fastify-postgres/test.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use strict";
2+
3+
const tap = require("tap");
4+
const fastify = require("./server");
5+
6+
tap.test("should test all API endpoint", async (t) => {
7+
let book = {};
8+
9+
const app = await fastify();
10+
11+
t.teardown(() => app.close());
12+
// only to test error path
13+
t.test("POST / should success create item", async (t) => {
14+
const response = await app.inject({
15+
method: "POST",
16+
url: "/",
17+
payload: {
18+
title: "Hello, World!",
19+
},
20+
});
21+
const json = response.json();
22+
t.equal(response.statusCode, 200);
23+
t.equal(json[0].title, "Hello, World!");
24+
// assign created to use as compare value
25+
book = json[0];
26+
});
27+
28+
t.test("GET / should success return items", async (t) => {
29+
const response = await app.inject({
30+
method: "GET",
31+
url: "/",
32+
});
33+
const json = response.json();
34+
t.equal(response.statusCode, 200);
35+
t.equal(json.length > 0, true);
36+
t.equal(json[0].title, "Hello, World!");
37+
});
38+
39+
t.test("GET /:id should success return item", async (t) => {
40+
const response = await app.inject({
41+
method: "GET",
42+
url: `/${book.id}`,
43+
});
44+
const json = response.json();
45+
t.equal(response.statusCode, 200);
46+
t.equal(json[0].title, "Hello, World!");
47+
});
48+
49+
t.test("UPDATE /:id should success return item", async (t) => {
50+
const response = await app.inject({
51+
method: "PATCH",
52+
url: `/${book.id}`,
53+
payload: {
54+
title: "Hello again, World!",
55+
},
56+
});
57+
const json = response.json();
58+
t.equal(response.statusCode, 200);
59+
t.equal(json[0].title, "Hello again, World!");
60+
});
61+
62+
t.test("DELETE /:id should success delete item", async (t) => {
63+
const response = await app.inject({
64+
method: "DELETE",
65+
url: `/${book.id}`,
66+
});
67+
const json = response.json();
68+
t.equal(response.statusCode, 200);
69+
t.equal(json.rowCount, 1);
70+
});
71+
});

0 commit comments

Comments
 (0)