- Overview
- Get Started
- Writing Tests with Tap
- Logging with Pino 7+
- Unicode and Windows Terminal
- Code Style
This starter template includes tap for tests and pino for logging.
ESLint is configured for Typescript in Standard.js semi style (semicolons) and a few quality of life modifications.
Standard.js is widely used at vercel, heroku, node, elastic, npm, nearform, fastify and more!
- Generate new repository from this template: https://github.com/VIEWVIEWVIEW/sensible-typescript-starter-node/generate
- Clone repo to your local machine:
git clone https://github.com/username/generated-repo
- Run
npm i
and install the ESLint Extension for vscode.
(ESLint Extension is included in the.vscode
workspace recommendations too!)
Tests are written with the tap testing framework.
Put all your tests into test/
and make sure your file name ends in *.test.ts
.
To run a your tap-tests, simply execute npm run test
.
When the output in the terminal gets to messy, you can also let tap create a coverage report, which you can inspect in the browser: npm run coverage
. This will create a new folder called coverage
with an index.html
.
You can inspect a screenshot of a sample output here:
// /test/Animal.test.ts
import tap from 'tap';
import { Cat, Dog } from '../src/Animal';
tap.test('Cat', async (t) => {
const testCat = new Cat();
t.equal(testCat.playSound(), 'meow');
t.equal(testCat.sleep(), 'π€');
});
tap.test('Dog', async (t) => {
const testDog = new Dog();
t.equal(testDog.playSound(), 'woof');
t.equal(testDog.sleep(), 'π€');
});
I strongly recommend checking out the pino docs on https://getpino.io/, but here is a quick overview of the essentials:
You can set your options in src/util/logger.ts
. You will most likely only use the level, which you can also set with the env var LOG_LEVEL
. Check out the .env.example
for more information and the pino logger options if you need child loggers, custom levels or redactions: https://getpino.io/#/docs/api?id=options
const log = pino({
level: 'error', // One of 'fatal', 'error', 'warn', 'info', 'debug', 'trace' or 'silent'.
})
Use a separate tool for log rotation, e.g. logrotate.
Consider we output our logs to /var/log/myapp.log
like so:
$ npm start > /var/log/myapp.log
We would rotate our log files with logrotate, by adding the following to /etc/logrotate.d/myapp
:
/var/log/myapp.log {
su root
daily
rotate 7
delaycompress
compress
notifempty
missingok
copytruncate
}
The copytruncate
configuration has a very slight possibility of lost log lines due
to a gap between copying and truncating - the truncate may occur after additional lines
have been written. To perform log rotation without copytruncate
, see Reopening log files in the pino help documentation.
Pino uses sonic-boom to speed up logging. Internally, it uses fs.write to write log lines directly to a file descriptor. On Windows, unicode output is not handled properly in the terminal (both cmd.exe and powershell), and as such the output could be visualized incorrectly if the log lines include utf8 characters. It is possible to configure the terminal to visualize those characters correctly with the use of chcp by executing in the terminal
chcp 65001
. This is a known limitation of Node.js.
https://github.com/pinojs/pino/blob/master/docs/help.md#unicode-and-windows-terminal
This repo uses a modified version of semistandard aiming for best practices and a modern ecmascript experience. Three rules in total were modified.
- To lint your code, run
npm run lint
- To lint and fix your code, run
npm lint:fix
The following modifications are applied in .eslintrc.js
:
'@typescript-eslint/semi': ['error', 'always']
- semicolon at end of line
// β
console.log('hello world')
// β
console.log('hello world');
'comma-dangle': ['error', { arrays: 'always-multiline', objects: 'always-multiline' }]
comma at the end of multi line arrays and objects
// β
const arr = [1,2,];
// β
const foo = {
bar: 'baz',
qux: 'quux'
};
// β
const arr = [1,2,];
// β
foo({
bar: 'baz',
qux: 'quux',
});
// β
const arr = [
1,
2,
];
curly: ['error', 'multi', 'consistent']
// β
if (true) { return false; }
// β
if (true)
return false;
// β
if (true) {
foo();
return true;
}