Fastify starter
The purpose of this repository is to provide instructions to create and configure a new Fastify app from scratch with appropriate linters, editor config, testing utilities, continuous integration.
Table of contents
- Prerequisites
- Quickstart
- Manual configuration
- Init the project
- Create default app
- Install Jest & testing utilities
- Install Prettier code formatter
- Install ESLint code linter with StandardJS rules
- Install MarkdownLint code linter
- Install npm-check dependencies checker
- Install dotenv-flow
- Configure .gitignore
- Configure .editorconfig
- Configure CI with Git hooks
- Configure CI with GitHub Actions
- Integrate formatters, linters & syntax to VSCode
- Usage
Prerequisites
- Git v2
- NodeJS v12
- NPM v6
- MongoDB v4.2
Quickstart
# Clone repo
git clone https://github.com/RomainFallet/fastify-starter
# Go inside the project
cd ./fastify-starter
# Install dependencies
npm install
# Create database (replace <dbname>)
mongo --eval "db = db.getSiblingDB('<dbname>')"
# Create a user and grant him access to the db
# (replace <username>, <password> and <dbname>)
mongo --eval "db.createUser(
{
user: '<username>',
pwd: '<password>',
roles: [ { role: 'readWrite', db: '<dbname>' } ]
}
)"
# Load fixtures (replace <dbname>)
mongo <dbname> --eval "$(cat ./fixtures/*)"
Then, copy the ./.env
file to ./.env.local
and replace variables:
MONGODB_URI=mongodb://<username>:<password>@localhost:27017/<dbname>
Manual configuration
Init the project
Create a new ./.npmrc
file:
save-prefix='~'
Create a new ./package.json
file:
{
"name": "fastify-starter",
"version": "1.0.0",
"license": "UNLICENSED",
"repository": "git@github.com:RomainFallet/fastify-starter.git",
"scripts": {
"start": "nodemon ./src/index.js",
"test": "is-ci test:all test:watch",
"test:watch": "jest --watch",
"test:all": "jest --passWithNoTests",
"deps:check": "npm-check",
"deps:upgrade": "npm-check -u",
"lint": "npm-run-all lint:*",
"lint:js": "eslint \"./**/*.js\"",
"lint:json": "prettier --check \"./**/*.json\"",
"lint:md": "markdownlint \"./**/*.md\" --ignore ./node_modules",
"lint:yml": "prettier --check \"./**/*.yml\"",
"format": "npm-run-all format:*",
"format:json": "prettier --write \"./**/*.json\"",
"format:js": "eslint --fix \"./**/*.js\"",
"format:md": "markdownlint --fix \"./**/*.md\" --ignore ./node_modules",
"format:yml": "prettier --write \"./**/*.yml\""
},
"lint-staged": {
"./**/*.json": [
"prettier --check"
],
"./**/*.js": [
"eslint"
],
"./**/*.yml": [
"prettier --check"
],
"./**/*.md": [
"markdownlint --ignore ./node_modules"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
Install packages:
# Dependencies
npm install fastify@~2.13.0 axios@~0.19.0 mongodb@~3.5.0 mongoose@~5.9.0 dotenv-flow@~3.1.0
# Dev dependencies
npm install --save-dev nodemon@~2.0.0 npm-run-all@~4.1.5 is-ci-cli@~2.0.0
Create default app
Create a new ./src/index.js
file:
require('dotenv-flow').config()
const mongoose = require('mongoose')
const app = require('fastify')({
logger: true
})
const start = async () => {
try {
// Connect to the database
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
serverSelectionTimeoutMS: 1000
})
// Register routes
app.register(require('./routes/cats'))
// Start the webserver
await app.listen(3000)
app.log.info(`server listening on ${app.server.address().port}`)
} catch (err) {
app.log.error(err)
process.exit(1)
}
}
start()
Create a new file ./src/routes/cats.js
:
const Cat = require('../models/cat')
module.exports = async app => {
app.get('/cats', async () => {
const cats = await Cat.find({})
return cats.map(cat => cat.toJSON())
})
app.post('/cats', async (req, res) => {
await Cat.create(req.body)
res.status(204)
})
}
Create a new file ./src/models/cat.js
:
const mongoose = require('mongoose')
const schema = new mongoose.Schema(
{
name: String,
color: String
},
{ timestamps: true }
)
module.exports = mongoose.model('Cat', schema)
Create a new file ./.env
:
MONGODB_URI=mongodb://<username>:<password>@localhost:27017/<database>
Create a new ./fixtures/cat.js
file:
/* eslint-env mongo */
// eslint-disable-next-line no-global-assign
ObjectId =
typeof ObjectId !== 'undefined'
? ObjectId
: require('mongoose').Types.ObjectId
const cat = {
_id: ObjectId('5e980b9bafdcc9eda8df1ffc'),
__v: 0,
name: 'Kitty',
color: 'brown',
createdAt: new Date('2020-04-16T08:57:33.198Z'),
updatedAt: new Date('2020-04-16T08:57:33.198Z')
}
if (typeof db !== 'undefined') {
db.cats.remove({})
db.cats.insert(cat)
}
module.exports = cat
Install Jest & testing utilities
Install packages:
npm install --save-dev jest@~25.2.0 axios-mock-adapter@~1.18.0 mongodb-memory-server@~6.5.0
Create a new ./jest.config.js
file:
module.exports = {
testEnvironment: "node",
}
Create a new ./src/helpers/test-utils.js
:
const { MongoMemoryServer } = require('mongodb-memory-server')
const mongoose = require('mongoose')
const setupMongo = async () => {
const mongoServer = new MongoMemoryServer({ autoStart: false })
const connection = await mongoose.connect(await mongoServer.getUri(), {
useNewUrlParser: true,
useUnifiedTopology: true,
serverSelectionTimeoutMS: 100
})
return { mongoServer, connection }
}
const cleanMongo = async ({ mongoServer, connection }) => {
await mongoServer.stop()
await connection.disconnect()
}
module.exports = { setupMongo, cleanMongo }
Create a new ./src/routes/cat.test.js
file:
const fastify = require('fastify')
const { setupMongo, cleanMongo } = require('../helpers/test-utils')
const Cat = require('../models/cat')
const mongoose = require('mongoose')
const catsRoute = require('./cats')
const cat = require('./../../fixtures/cat')
describe('/cats', () => {
describe('GET /cats', () => {
it('responds 200 and return cats', async () => {
// Arrange
expect.assertions(2)
const app = fastify().register(catsRoute)
const { mongoServer, connection } = await setupMongo()
await Cat.create(cat)
// Act
const res = await app.inject({
method: 'GET',
url: '/cats'
})
// Clean up
await cleanMongo({ mongoServer, connection })
// Assert
expect(res.statusCode).toBe(200)
expect(res.json()).toStrictEqual([
{
_id: cat._id.toString(),
__v: cat.__v,
name: cat.name,
color: cat.color,
createdAt: cat.createdAt.toISOString(),
updatedAt: cat.updatedAt.toISOString()
}
])
})
})
describe('POST /cats', () => {
it('responds 204 and save cat', async () => {
// Arrange
expect.assertions(3)
const app = fastify().register(catsRoute)
const { mongoServer, connection } = await setupMongo()
// Act
const res = await app.inject({
method: 'POST',
url: '/cats',
body: { name: 'Meow', color: 'dark' }
})
const savedCat = (await Cat.findOne({ name: 'Meow' })).toObject()
// Clean up
await cleanMongo({ mongoServer, connection })
// Assert
expect(res.statusCode).toBe(204)
expect(res.body).toBe('')
expect(savedCat).toStrictEqual({
_id: expect.any(mongoose.Types.ObjectId),
__v: 0,
color: 'dark',
name: 'Meow',
createdAt: expect.any(Date),
updatedAt: expect.any(Date)
})
})
})
})
Install Prettier code formatter
# Install Prettier with StandardJS config
npm i -D prettier@~2.0.0 prettier-config-standard@~1.0.0
# Install configs for ESLint integration
npm i -D eslint-plugin-prettier@~3.1.0 eslint-config-prettier@~6.10.0 eslint-config-prettier-standard@~3.0.0
Install ESLint code linter with StandardJS rules
# Install ESLint
npm i -D eslint@~6.8.0
# Install ESLint default plugins
npm i -D eslint-plugin-promise@~4.2.0 eslint-plugin-import@~2.20.0 eslint-plugin-node@~11.1.0
# Install StandardJS & Jest plugins
npm i -D eslint-plugin-standard@~4.0.0 eslint-plugin-jest@~23.8.0
# Install StandardJS config
npm i -D eslint-config-standard@~14.1.0
Create a new ./.eslintrc.json
file:
{
"extends": ["standard", "prettier-standard"],
"overrides": [
{
"files": ["./src/**/*.test.js"],
"extends": "plugin:jest/all"
}
],
"plugins": ["jest"]
}
Install MarkdownLint code linter
npm install --save-dev markdownlint@~0.19.0 markdownlint-cli@~0.22.0
Create a new ./.markdownlint.json
file:
{
"default": true
}
Install dependencies checker
npm install --save-dev npm-check@~5.9.0
Configure .gitignore
Create a new ./.gitignore
file:
# OS Specific
.DS_Store
# Dependencies
node_modules
# Environment
.env.local
Configure .editorconfig
Create a new ./.editorconfig
file:
# EditorConfig is awesome: https://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
Configure CI with Git hooks
npm install --save-dev husky@~4.2.0 lint-staged@~10.1.0
Configure CI with GitHub Actions
Create a new ./.github/workflows/lint.yml
file:
name: Check coding style and lint code
on: ["push", "pull_request"]
jobs:
lint:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Cache node modules
uses: actions/cache@v1
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ env.cache-name }}-${{ hashFiles('./package-lock.json') }}
restore-keys: ${{ env.cache-name }}-
- name: Install dependencies
run: npm install
- name: Check coding style and lint code
run: npm run lint
Create a new ./.github/workflows/test.yml
file:
name: Launch unit tests
on: ["pull_request"]
jobs:
test:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Cache node modules
uses: actions/cache@v1
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ env.cache-name }}-${{ hashFiles('./package-lock.json') }}
restore-keys: ${{ env.cache-name }}-
- name: Install dependencies
run: npm install
- name: Launch test with Jest
run: npm test
Integrate formatters, linters & syntax to VSCode
Create a new ./.vscode/extensions.json
file:
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"davidanson.vscode-markdownlint",
"me-dutour-mathieu.vscode-github-actions",
"mikestead.dotenv",
"editorconfig.editorconfig",
"eg2.vscode-npm-script"
]
}
This will suggest to install npm, Prettier, ESLint, MarkdownLint, Github Actions, DotENV and EditorConfig extensions to everybody opening this project in VSCode.
Then, create a new ./.vscode/settings.json
file:
{
"eslint.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.markdownlint": true
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.disableLanguages": ["javascript", "markdown"]
}
This will format automatically the code on save.
Usage
Launch app
npm start
Launch unit tests
# Test only changes since last commit in watch mode
npm test
# Run all test suite
npm run test:all
Check coding style & Lint code for errors/bad practices
# Check all files
npm run lint
# Check JavaScript with ESLint (Prettier + StandardJS)
npm run lint:js
# Check JSON with Prettier
npm run lint:json
# Check YAML with Prettier
npm run lint:yml
# Check Mardkown with MarkdownLint
npm run lint:md
Format code automatically
# Format all files
npm run format
# Format JavaScript with ESLint (Prettier + StandardJS)
npm run format:js
# Format JSON with Prettier
npm run format:json
# Format YAML with Prettier
npm run format:yml
# Format Mardkown with MarkdownLint
npm run format:md
Audit & fix dependencies vulnerabilities
# Check for known vulnerabilities in dependencies
npm audit
# Install latest patches of all dependencies
npm update
Check & upgrade outdated dependencies
# Check for unused/outdated dependencies
npm run deps:check
# Choose interactively which dependency to upgrade
npm run deps:upgrade