Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide example using database with 100% test coverage #18

Merged
merged 12 commits into from
Nov 1, 2021
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Here a list of the projects with a description, search in this page what you are
| [validation-messages] | `schema` `validation` | How you can customize the error messages of input schema validation |
| [winston-logger] | `logger` | Example how to use winston as a custom logger |
| [typescript decorators] | `typescript` | Example how to use typescript decorators to build application |
| [fastify postgres] | `postgres` `crud` | Simple CRUD app that show how integrate fastify with database, with 100% test coverage |
| [tests] | `tests` | Example of how to test your fastify application |


Expand Down Expand Up @@ -44,3 +45,4 @@ Licensed under [MIT](./LICENSE).
[validation-messages]:./validation-messages/
[winston-logger]: ./winston-logger
[typescript decorators]: ./typescript-decorators
[fastify postgres]: /.fastify-postgres
12 changes: 12 additions & 0 deletions fastify-postgres/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:14-alpine

WORKDIR /src
COPY package.json /
EXPOSE 3000

# on CI normaly you use npm ci / npm clean-install
RUN npm install
COPY . /
CMD ["npm", "start"]


38 changes: 38 additions & 0 deletions fastify-postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Fastify Postgres CRUD

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.

## Requirements

- Docker & Docker Compose
- Node.js

## How to run?

With npm

```
$ npm start:db // start postgres db with docker
$ npm install
$ npm start

# testing
$ npm test
```

With docker compose

```
# migration up
$ docker-compose -f docker-compose.yaml -f compose-file/migration-up.yaml --exit-code-from fastify_postgres

# run the app
$ docker-compose up

# testing
$ docker-compose -f docker-compose.yaml -f compose-file/test.yaml --exit-code-from fastify_postgres

# migration down
$ docker-compose -f docker-compose.yaml -f compose-file/migration-up.yaml --exit-code-from fastify_postgres

```
5 changes: 5 additions & 0 deletions fastify-postgres/compose-file/migration-down.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3.8"
services:
fastify_postgres:
command: >
sh -c "npm run migration:down"
5 changes: 5 additions & 0 deletions fastify-postgres/compose-file/migration-up.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3.8"
services:
fastify_postgres:
command: >
sh -c "npm run migration:up"
5 changes: 5 additions & 0 deletions fastify-postgres/compose-file/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3.8"
services:
fastify_postgres:
command: >
sh -c "npm test"
29 changes: 29 additions & 0 deletions fastify-postgres/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
version: "3.8"
services:
db:
image: postgres:latest
expose:
- "5432"
ports:
- "5432:5432"
environment:
- POSTGRES_DB=fastify_postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres

fastify_postgres:
build:
context: ./
dockerfile: Dockerfile
volumes:
- .:/src
command: >
sh -c "npm start"
expose:
- "3000"
ports:
- "3000:3000"
depends_on:
- db
environment:
- DATABASE_URL=postgresql://postgres:postgres@db_postgres:5432/fastify_postgres?schema=public
14 changes: 14 additions & 0 deletions fastify-postgres/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use strict";

const app = require("./server")({ logger: true });

const start = async () => {
try {
await app.listen(3000);
} catch (err) {
app.log.error(err);
process.exit(1);
}
};

start();
27 changes: 27 additions & 0 deletions fastify-postgres/migration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use strict";

const Postgrator = require("postgrator");

const connectionString =
process.env.DATABASE_URL ||
"postgresql://postgres:postgres@localhost:5432/fastify_postgres?schema=public";

const postgrator = new Postgrator({
connectionString: connectionString,
migrationDirectory: __dirname + "/migrations",
driver: "pg",
});

if (process.env.MIGRATE_ACTION === "do") {
postgrator
.migrate()
.then((appliedMigrations) => console.log(appliedMigrations))
.catch((error) => console.log(error));
}

if (process.env.MIGRATE_ACTION === "undo") {
postgrator
.migrate("000")
.then((appliedMigrations) => console.log(appliedMigrations))
.catch((error) => console.log(error));
}
4 changes: 4 additions & 0 deletions fastify-postgres/migrations/001.do.books.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS books (
id serial PRIMARY KEY,
title varchar (255) NOT NULL
)
1 change: 1 addition & 0 deletions fastify-postgres/migrations/001.undo.books.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS books
27 changes: 27 additions & 0 deletions fastify-postgres/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "fastify-database-example",
"version": "1.0.0",
"description": "Example on how to use, fastify with postgres database unit and integration test",
"main": "index.js",
"scripts": {
"test": "tap",
"start": "node index.js",
"start:db": "docker-compose up -d db",
"migrate:up": "MIGRATE_ACTION=do node migration.js",
"migrate:down": "MIGRATE_ACTION=undo node migration.js"
},
"keywords": [
"postgres",
"fastify"
],
"author": "Manda Putra <manda@omg.lol> (https://github.com/mandaputtra)",
"license": "MIT",
"dependencies": {
"fastify": "^3.20.2",
"fastify-postgres": "^3.6.0",
"pg": "^8.7.1"
},
"devDependencies": {
"tap": "^15.0.9"
}
}
69 changes: 69 additions & 0 deletions fastify-postgres/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"use strict";

const fastify = require("fastify");

const connectionString =
process.env.DATABASE_URL ||
"postgresql://postgres:postgres@localhost:5432/fastify_postgres?schema=public";

function build(opts = {}) {
const app = fastify(opts);

app.register(require("fastify-postgres"), { connectionString });

app.get("/", async () => {
const client = await app.pg.connect();
const { rows } = await client.query("SELECT * FROM books");
client.release();
return rows;
});

app.post("/", async (request) => {
const { body } = request;
const client = await app.pg.connect();
const { rows } = await client.query(
"INSERT INTO books (title) VALUES ($1) RETURNING *",
[body.title]
);
client.release();
return rows;
});

app.get("/:id", async (request) => {
const { params } = request;
const client = await app.pg.connect();
const { rows } = await client.query("SELECT * FROM books WHERE id = $1", [
+params.id,
]);
client.release();
return rows;
});

app.patch("/:id", async (request) => {
const { params, body } = request;
return app.pg.transact(async (client) => {
await client.query("UPDATE books SET title = $1 WHERE id = $2", [
body.title,
+params.id,
]);
const { rows } = await client.query("SELECT * FROM books WHERE id = $1", [
+params.id,
]);
return rows;
});
});

app.delete("/:id", async (request) => {
const { params } = request;
const client = await app.pg.connect();
const { rowCount } = await client.query("DELETE FROM books WHERE id = $1", [
+params.id,
]);
client.release();
return { rowCount };
});

return app;
}

module.exports = build;
71 changes: 71 additions & 0 deletions fastify-postgres/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use strict";

const tap = require("tap");
const fastify = require("./server");

tap.test("should test all API endpoint", async (t) => {
let book = {};

const app = await fastify();

t.teardown(() => app.close());
// only to test error path
t.test("POST / should success create item", async (t) => {
const response = await app.inject({
method: "POST",
url: "/",
payload: {
title: "Hello, World!",
},
});
const json = response.json();
t.equal(response.statusCode, 200);
t.equal(json[0].title, "Hello, World!");
// assign created to use as compare value
book = json[0];
});

t.test("GET / should success return items", async (t) => {
const response = await app.inject({
method: "GET",
url: "/",
});
const json = response.json();
t.equal(response.statusCode, 200);
t.equal(json.length > 0, true);
t.equal(json[0].title, "Hello, World!");
});

t.test("GET /:id should success return item", async (t) => {
const response = await app.inject({
method: "GET",
url: `/${book.id}`,
});
const json = response.json();
t.equal(response.statusCode, 200);
t.equal(json[0].title, "Hello, World!");
});

t.test("UPDATE /:id should success return item", async (t) => {
const response = await app.inject({
method: "PATCH",
url: `/${book.id}`,
payload: {
title: "Hello again, World!",
},
});
const json = response.json();
t.equal(response.statusCode, 200);
t.equal(json[0].title, "Hello again, World!");
});

t.test("DELETE /:id should success delete item", async (t) => {
const response = await app.inject({
method: "DELETE",
url: `/${book.id}`,
});
const json = response.json();
t.equal(response.statusCode, 200);
t.equal(json.rowCount, 1);
});
});