Skip to content

Commit

Permalink
Create basic architecture (#1)
Browse files Browse the repository at this point in the history
* chore: create basic architecture

* fix: typo in comment

* chore: add server.js to show how to not depend on cli

* fix: should not try to parser string as json

* fix: typo comment

* chore: add helmet plugin

* chore: add cors plugin

* chore: add env plugin

* refactor: reorganize plugin structure

* chore: add swagger

* fix: typo comment

* fix: comment typo

* fix: swagger tag Example in /example

* refactor: simplify plugin structure folder

* refactor: put swagger registration in /plugins

* refactor: add pino-pretty to server.js

* chore: add error handlers

* refactor: unecessary await setNotFoundHandler

* test: root not found handler

* chore: reuse existing workflow

* test on v4

* chore: use ci v4

* fix: run lint fix

* fix: cant use import.meta.dirname for now

* update readme getting-started

* fix: node imports

* chore: use glob to launch tests

* fix: invalid workflow file

* chore: use glob to run test suite

* chore: use typescript

* chore: use typescript-eslint with prettier

* fix: add blank line to .prettierignore

* chore: add under-pressure plugin

* docs: add links to under-pressure plugin

* refactor: custom plugins should have a name

* chore: setup db

* chore: remove phpmyadmin from docker composition

* fix: typo

* refactor: create src folder and run test folder with tap

* chore: remove glob

* test: error-handler still todo

* fix: remove @types from tsconfig include

* fix: compiler config

* chore: get rid of NODE_ENV

* fix: remove commented code

* refactor: move under-pressure options configuration inside plugin definition

* refactor: leverage import.meta.dirname

* test: POST /example and error-handler

* chore: pretty-logger only if the program is running in an interactive terminal

* fix: fp must be used to override default error handler

* chore: update CI

* support only ubuntu

* dont wait for mysql to be ready

* lint

* refactor: uninstall global listeners is not needed

* refactor: use neostandard

* refactor: add external plugins in their own folder

* docs: highlights the concept of modular monolyth

* chore: dont wait for MySQL to be ready in ci

* chore: support only ubuntu-latest os

* refactor: leverage autoConfig

* fix: add blank line

* chore: raise maxRssBytes to 1GB

* chore: dhould not log during tests

* refactor: launch server with await instead of callback

* docs: add .env.example file

* fix: remove body-limit test

* refactor: leverage autoConfig callback feature

* fix: add blank line

* chore: remove build command before starting server related scripts

* feat: add basic jwt authentication

* refactor: return username in /api response

* chore: use of postgrator for db migrations

* chore: add JWT_SECRET generation step for CI env

* feat: create repository plugin to simplify queries

* fix: remove unused param con of doMigration

* refactor: no need for seed database at the beggining of a test

* test: ignore seed:db catch blocks

* fix: typo

* refactor: move type declarations outside plugin declaration

* chore: generate dummy .env during CI for scripts using -env-file=.env flag

* chore: can't --ignore-scripts if using bcrypt during CI

* refactor: use IAuth interface tp type user on request

* fix: add blank line to migration files

* fix: remove useless comments

* refactor: declare type decoration of fastify instance in plugin files

* fix: add blank line

* feat: create scrypt plugin

* feat: create scrypt plugin

* fix: add blank line

* fix: revert timingSafeEqual in error branch

* Update src/plugins/custom/repository.ts

Co-authored-by: KaKa <[email protected]>
Signed-off-by: Jean <[email protected]>

* refactor: remove I prefix from ts interface

* fix: identifier typo

* fix: remove useless comment

* fix: run migration before test

* test: ensure access-control-allow-methods contains the expected methods

* docs: add @link tag to urls

* Update src/app.ts

Co-authored-by: Frazer Smith <[email protected]>
Signed-off-by: Jean <[email protected]>

* fix: test name

---------

Signed-off-by: Jean <[email protected]>
Co-authored-by: KaKa <[email protected]>
Co-authored-by: Frazer Smith <[email protected]>
  • Loading branch information
3 people authored Jul 26, 2024
1 parent e049e70 commit 0e6dff1
Show file tree
Hide file tree
Showing 51 changed files with 1,488 additions and 1 deletion.
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Must always set to production
# @see {@link https://www.youtube.com/watch?v=HMM7GJC5E2o}
NODE_ENV=production

# Database
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=test_db
MYSQL_USER=test_user
MYSQL_PASSWORD=test_password

# Server
FASTIFY_CLOSE_GRACE_DELAY=1000
LOG_LEVEL=info

# Security
JWT_SECRET=
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10

- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
21 changes: 21 additions & 0 deletions .github/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 15
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- "discussion"
- "feature request"
- "bug"
- "help wanted"
- "plugin suggestion"
- "good first issue"
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: CI

on:
push:
branches:
- main
- next
- "v*"
paths-ignore:
- "docs/**"
- "*.md"
pull_request:
paths-ignore:
- "docs/**"
- "*.md"

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22]

services:
mysql:
image: mysql:8.4
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_password
options: >-
--health-cmd="mysqladmin ping -u$MYSQL_USER -p$MYSQL_PASSWORD"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: npm i

- name: Lint Code
run: npm run lint

- name: Generate JWT Secret
id: gen-jwt
run: |
JWT_SECRET=$(openssl rand -hex 32)
echo "JWT_SECRET=$JWT_SECRET" >> $GITHUB_ENV
- name: Generate dummy .env for scripts using -env-file=.env flag
run: touch .env

- name: Test
env:
MYSQL_HOST: localhost
MYSQL_PORT: 3306
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_password
# JWT_SECRET is dynamically generated and loaded from the environment
run: npm run db:migrate && npm run test
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ coverage
# nyc test coverage
.nyc_output

# tap test coverage
.tap

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

Expand Down Expand Up @@ -128,3 +131,9 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# lock files
bun.lockb
package-lock.json
pnpm-lock.yaml
yarn.lock
7 changes: 7 additions & 0 deletions @types/fastify/fastify.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Auth } from "../../src/schemas/auth.ts";

declare module "fastify" {
export interface FastifyRequest {
user: Auth
}
}
16 changes: 16 additions & 0 deletions @types/node/environment.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
PORT: number;
LOG_LEVEL: string;
FASTIFY_CLOSE_GRACE_DELAY: number;
MYSQL_HOST: string
MYSQL_PORT: number
MYSQL_DATABASE: string
MYSQL_USER: string
MYSQL_PASSWORD: string
}
}
}

export {};
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# demo
# Fastify Official Demo

![CI](https://github.com/fastify/demo/workflows/CI/badge.svg)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)

> :warning: **Please note:** This repository is still under active development.
The aim of this repository is to provide a concrete example of a Fastify application using what are considered best practices by the Fastify community.

**Prerequisites:** You need to have Node.js version 22 or higher installed.

## Getting started

Install the dependencies:

```bash
npm install
```

## Available Scripts

In the project directory, you can run:

### `npm run dev`

To start the app in dev mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.

### `npm start`

For production mode

### `npm run test`

Run the test cases.

## Learn More

To learn Fastify, check out the [Fastify documentation](https://fastify.dev/docs/latest/).
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
db:
image: mysql:8.4
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- 3306:3306
volumes:
- db_data:/var/lib/mysql

volumes:
db_data:
20 changes: 20 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

import neo from 'neostandard'

export default [
...neo({
ts: true
}),
{
rules: {
'@stylistic/comma-dangle': ['error', {
arrays: 'never',
objects: 'never',
imports: 'never',
exports: 'never',
functions: 'never'
}]
}
}
]
7 changes: 7 additions & 0 deletions migrations/001.do.users.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
1 change: 1 addition & 0 deletions migrations/001.undo.users.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS users;
11 changes: 11 additions & 0 deletions migrations/002.do.tasks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
author_id INT NOT NULL,
assigned_user_id INT,
status VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id),
FOREIGN KEY (assigned_user_id) REFERENCES users(id)
);
1 change: 1 addition & 0 deletions migrations/002.undo.tasks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS tasks;
7 changes: 7 additions & 0 deletions migrations/003.do.user_tasks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE user_tasks (
user_id INT NOT NULL,
task_id INT NOT NULL,
PRIMARY KEY (user_id, task_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
1 change: 1 addition & 0 deletions migrations/003.undo.user_tasks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS user_tasks;
52 changes: 52 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "fastify-demo",
"version": "0.0.0",
"description": "The official Fastify demo!",
"main": "app.js",
"type": "module",
"directories": {
"test": "test"
},
"scripts": {
"build": "rm -rf dist && tsc",
"watch": "npm run build -- --watch",
"test": "npm run db:seed && tap --jobs=1 test/**/*",
"start": "fastify start -l info dist/app.js",
"dev": "fastify start -w -l info -P dist/app.js",
"standalone": "node --env-file=.env dist/server.js",
"lint": "eslint --ignore-pattern=dist",
"lint:fix": "npm run lint -- --fix",
"db:migrate": "node --env-file=.env scripts/migrate.js",
"db:seed": "node --env-file=.env scripts/seed-database.js"
},
"keywords": [],
"author": "Michelet Jean <[email protected]>",
"license": "MIT",
"dependencies": {
"@fastify/autoload": "^5.10.0",
"@fastify/cors": "^9.0.1",
"@fastify/env": "^4.3.0",
"@fastify/helmet": "^11.1.1",
"@fastify/jwt": "^8.0.1",
"@fastify/mysql": "^4.3.0",
"@fastify/sensible": "^5.0.0",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^3.0.0",
"@fastify/type-provider-typebox": "^4.0.0",
"@fastify/under-pressure": "^8.3.0",
"@sinclair/typebox": "^0.32.31",
"fastify": "^4.26.1",
"fastify-cli": "^6.1.1",
"fastify-plugin": "^4.0.0",
"postgrator": "^7.2.0"
},
"devDependencies": {
"@types/node": "^20.14.2",
"eslint": "^9.4.0",
"fastify-tsconfig": "^2.0.0",
"mysql2": "^3.10.1",
"neostandard": "^0.7.0",
"tap": "^19.2.2",
"typescript": "^5.4.5"
}
}
39 changes: 39 additions & 0 deletions scripts/migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import mysql from 'mysql2/promise'
import path from 'path'
import Postgrator from 'postgrator'

async function doMigration () {
const connection = await mysql.createConnection({
multipleStatements: true,
host: process.env.MYSQL_HOST,
port: process.env.MYSQL_PORT,
database: process.env.MYSQL_DATABASE,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD
})

const postgrator = new Postgrator({
migrationPattern: path.join(import.meta.dirname, '../migrations', '*'),
driver: 'mysql',
database: process.env.MYSQL_DATABASE,
execQuery: async (query) => {
const [rows, fields] = await connection.query(query)

return { rows, fields }
},
schemaTable: 'schemaversion'
})

await postgrator.migrate()

await new Promise((resolve, reject) => {
connection.end((err) => {
if (err) {
return reject(err)
}
resolve()
})
})
}

doMigration().catch(err => console.error(err))
Loading

0 comments on commit 0e6dff1

Please sign in to comment.