Yet another (opinionated) TypeScript service starter template.
- Follows Domain Driven Development and 3 Layers architecture
- As little of externalities requirements as possible (outputs to stdout/stderr, no auth management, optional cache, etc.)
- No dependency on node_modules folder and filesystem at runtime, to allow bundling & small Docker image
- Config should not default to either development or production (link)
And extends the ones from typescript-library-starter
- Relies as much as possible on each included library's defaults
- Only relies on GitHub Actions
- Does not include documentation generation
- Always sets NODE_ENV to production and use ENV_NAME for logging purposes
npx degit gjuchault/typescript-service-starter my-project
or click on theUse this template
button on GitHub!cd my-project
npm install
git init
(if you used degit)node --run setup
echo "HTTP_COOKIE_SIGNING_SECRET=$(node --run generate-secret)" > .env.local
To enable deployment, you will need to:
- Set up the
NPM_TOKEN
secret in GitHub Actions (Settings > Secrets > Actions) - Give
GITHUB_TOKEN
write permissions for GitHub releases (Settings > Actions > General > Workflow permissions)
This template is based on Fastify with some nice defaults (circuit breaker, redis rate limit, etc.). openapi-typescript is used to have nice routes & automatic client generation with zod and TypeScript.
Client should be published when this package is released. You can use openapi-fetch easily with it:
import createClient from "openapi-fetch";
import type { paths as api } from "typescript-service-starter";
export const client = createClient<api>({ baseUrl: "" });
const res = await client.GET("/api/docs");
You can check openapi-ts's documentation for more details.
Commands:
generate-client
: creates theclient/
folder with YAML and JSON OpenAPI schemas as well as openapi-typescript' schemas
It leverages PostgreSQL as a storage (through slonik), umzug for migrations, Redis (or compatible like KeyDB or Dragonfly) as a cache through ioredis.
Commands:
migrate:up
: run migrations up to the latest onemigrate:down [n=1]
: revert n migrationsmigrate:create
: create a new migration file
For the logging & telemetry part, it uses pino and OpenTelemetry (for both tracing and metrics). To handle distributed tracing, it expects W3C's traceparent header to carry trace id & parent span id (example header: traceparent: '00-82d7adc64d7020e7fe7ff263dd5ba4dc-dd932b8fbc70946d-01'
).
You can find an example of a working fullstack telemetry in docker-compose.yaml
, where this service directly pushes to prometheus and jaeger without the use of a collector.
This template is not compiled and should be run with Node --strip-types option. This means you can not leverage node_modules and file system at runtime: reading static files from node_modules, hooking require
, etc. ill not be possible. This implies to be mindful on libraries (that would read static files from there older), or automatic instrumentation (that hook require
). Yet it comes with super small Docker images hat are fast to deploy.
src/
├── contexts # contexts your service is handling (in the DDD sense)
│ ├── your-context
│ │ ├── application # service code
│ │ ├── domain # pure functions & TypeScript models of your entities
│ │ ├── presentation # communication layer (http)
│ │ ├── repository # storage of your entities
├── helpers # utilities functions & non-domain code
├── infrastructure # technical components (cache, database connection, etc.)
└── test-helpers # test utilities (starting default port, resetting database, etc.)
Dependencies are passed as last-parameter so methods are testable easily and avoid mocking modules.
TypeScript Service Starter relies on Volta to ensure Node.js version to be consistent across developers. It's also used in the GitHub workflow file.
Leverages --strip-types
to avoid build step, but keeps tsc
to generate .d.ts
files.
Commands:
start
: starts the bundled (see below) server with.env
and.env.local
env filesdev
: startssrc
server with.env
and.env.local
filesworker
: starts the worker with.env
,.env.local
env filestype:check
: validates types withtsc
This library is providing a very small Docker image thanks to bundling and code-splitting.
Running node --run bundle
will bundle all of the src/
and node_modules/
code into two files:
build/index.js
which runs the serverbuild/worker.js
which runs the task scheduler's worker
This will drastically improve the Docker image size: it only contains alpine, node, env files and the bundle files — no node_modules; but comes with one cost: dependencies can not base their code on a file structure containing a node_modules
folder (typically to find static files there)
Commands:
bundle
: creates thebuild/
directory containing the entrypoints and shared codestart:prod
: runs the http server entrypointworker:prod
: runs the worker entrypoint
TypeScript Library Starter uses Node.js's native test runner. Coverage is done using c8 but will switch to Node.js's one once out.
Commands:
test
: runs test runner for both unit and integration teststest:watch
: runs test runner in watch modetest:coverage
: runs test runner and generates coverage reports
This template relies on Biome to format and lint. It also uses cspell to ensure correct spelling.
Commands:
lint
: runs Biome with automatic fixinglint:check
: runs Biome without automatic fixing (used in CI)spell:check
: runs spell checking
Under the hood, this service uses semantic-release and Commitizen.
The goal is to avoid manual release processes. Using semantic-release
will automatically create a GitHub release (hence tags) as well as an npm release.
Based on your commit history, semantic-release
will automatically create a patch, feature or breaking release.
Commands:
cz
: interactive CLI that helps you generate a proper git commit message, using Commitizensemantic-release
: triggers a release (used in CI)