fastify-cli: running, scaffolding and debugging a Fastify app from one command
Run a Fastify application with one command!
At a glance
- What is it?
- fastify-cli is the official command line tool for Fastify, and it does three distinct jobs: it starts a plugin, it generates a project or a plugin, and it prints the router's internal trees for debugging. The interesting commands are the two print ones, because they expose Fastify's internals that are otherwise invisible.
- Who is it for?
- Adopt fastify-cli if you build Fastify services and want a single tool to start a plugin during development, scaffold new projects and inspect the router when a route does not match what you expected. Keep away if your deployment already has its own process manager and you need a production start command that does not depend on a global install, since the tool is installed globally and the eject command exists precisely to remove that dependency.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 19 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One binary with nine commands, three of them for debugging
fastify-cli is the command line tool for Fastify, and its stated purpose is to generate, write and run an application with one single command. Installation is a global npm install:
npm install fastify-cli --globalRunning it with no arguments prints the help, and the command list is worth reading because it shows the scope:
Fastify command line interface, available commands are:
* start start a server
* eject turns your application into a standalone executable with a server.(js|ts) file being added
* generate generate a new project
* generate-plugin generate a new plugin project
* generate-swagger generate Swagger/OpenAPI schema for a project using @fastify/swagger
* readme generate a README.md for the plugin
* print-routes prints the representation of the internal radix tree used by the router, useful for debugging.
* print-plugins prints the representation of the internal plugin tree used by avvio, useful for debugging.
* version the current fastify-cli version
* help help about commandsSix of those are about starting and scaffolding. The two print commands are the ones that are genuinely hard to get elsewhere, and they are described as useful for debugging.
print-routes prints the representation of the internal radix tree used by the router, and print-plugins prints the representation of the internal plugin tree used by avvio, which is the plugin loading mechanism in Fastify. Both expose structures that are otherwise invisible from application code, and knowing that a route landed in the tree where you expected, or that a plugin was encapsulated where you did not intend, is the kind of thing that otherwise takes a debugger and a lot of patience.
The default command is start, so fastify start plugin.js runs plugin.js. The project is MIT licensed, the default branch is main, the last push was on 2026-10-04, and the release list shows 8.0.1 on 2026-09-08, 8.0.2 on 2026-09-13 and 8.0.3 on 2026-09-30.
Starting a plugin, and the three module formats it accepts
The start command runs any Fastify plugin, and a plugin can be as simple as a function that registers a route:
// plugin.js
module.exports = function (fastify, options, next) {
fastify.get('/', function (req, reply) {
reply.send({ hello: 'world' })
})
next()
}The callback style with next is the older Fastify plugin shape. The README also covers Promises and async functions, where the plugin returns a value instead of calling next:
// async-await-plugin.js
module.exports = async function (fastify, options) {
fastify.get('/', async function (req, reply) {
return { hello: 'world' }
})
}The third format is an EcmaScript Module with a default export:
export default async function plugin (fastify, options) {
// Both `/foo` and `/foo/` are registered
fastify.get('/foo/', async function (req, reply) {
return 'foo'
})
}
export const options = {
ignoreTrailingSlash: true
}That example also shows two things at once. The comment notes that both /foo and /foo/ get registered, and the options export sets ignoreTrailingSlash, which is what makes that true.
The module format has version rules. Modules in EcmaScript Module format can be used on Node.js 14 or later, or on 12.17.0 and above but below 13.0.0. The .js extension works with Node.js 14 and later if the nearest parent package.json has type set to module, and if it does not, you use the .mjs extension instead.
The examples directory in the repository covers these shapes, with plugin.js, async-await-plugin.js, plugin-common-prefix.js, plugin-with-custom-options.js, plugin-with-env.js, plugin-with-logger.js, plugin-with-options.js, plugin-with-preloaded.js, several TypeScript variants including .mjs, and a package-type-module directory.
Options, config files and the precedence between them
The options surface is documented as a table, and every row has a short command, a full command and an environment variable, which means the same setting can come from three places.
The port defaults to 3000 and is set with -p or --port, with the environment variable FASTIFY_PORT or PORT. The address is -a or --address with FASTIFY_ADDRESS, and the socket is -s or --socket with FASTIFY_SOCKET. There is a path to a configuration file with -c or --config and the environment variable FASTIFY_CONFIG or CONFIG. Modules to preload are -r or --require with FASTIFY_REQUIRE, and ES Modules to preload are -i or --import with FASTIFY_IMPORT. The log level defaults to fatal and is -l or --log-level with FASTIFY_LOG_LEVEL, and a path to a logging configuration module is -L or --logging-module. Debug mode starts the app with the nodejs inspector and is -d or --debug, with the inspector port set separately and defaulting to 9320.
The precedence rule is the part that trips people up, and it is stated explicitly. You can pass a configuration file with --config or -c that exports all the properties in camelCase, and in case of collision, an argument existing in both the configuration file and as a command-line argument, the command-line argument is given the priority.
There is a second layer of precedence for plugin options, and it is different. If you want to use custom options for your plugin, you add them after the -- terminator, and if used in conjunction with the --options argument, the CLI arguments take precedence:
$ fastify start plugin.js -- --oneAnd the server creation options come from an exported options object on the plugin itself, which also gets passed to the plugin through the options argument. So a plugin can carry its own default configuration, the command line can override it, and a config file can override the defaults, in that order.
Generating projects, plugins, schemas and readmes
Four of the commands are about producing files, and they are what makes fastify-cli a scaffolding tool as well as a runner.
generate creates a new project and generate-plugin creates a new plugin project. generate-swagger produces a Swagger or OpenAPI schema for a project that uses @fastify/swagger, and readme generates a README.md for the plugin. The readme command in particular is a small quality-of-life feature that most frameworks do not bother with, since a plugin in the Fastify ecosystem is expected to ship documentation.
The repository structure shows how these are implemented, with one top level file per command. There is cli.js as the entry point, start.js, eject.js, generate.js, generate-plugin.js, generate-swagger.js, generate-readme.js, print-plugins.js and print-routes.js. The help/ directory holds the help text, the templates/ directory holds the project templates, and lib/ holds the shared library code. There is also args.js for argument parsing, env-loader.js for environment variables, log.js for logging, util.js for helpers, and a suite-runner.js with a should-skip-test-suites.js for the test suite.
The eject command is the one that matters for deployment. It turns your application into a standalone executable with a server.js or server.ts file being added, which is the escape hatch from depending on a globally installed CLI at runtime. That is the sensible production pattern, and the tool supports it rather than pretending the global install is the answer.
The package manifest shows the module layout. The package is type commonjs with cli.js as main and the fastify binary pointing at the same file, and the files are all at the top level rather than in a src directory, which is consistent with a tool that is a set of scripts.
The test suite runs the templates in four flavours
The test scripts in the package manifest are the clearest evidence of how seriously this project takes its own templates.
The unit suites run against the generated templates rather than against a hand-written fixture, in four combinations. The CommonJS template tests run as unit:cjs, the EcmaScript Module template as unit:esm, the TypeScript CommonJS template as unit:ts-cjs, and the TypeScript ES module template as unit:ts-esm. There is a unit:suites step that decides whether the suites should be skipped at all, and an all-suites target that runs all four in sequence.
Each variant needs its own invocation, and the TypeScript ones are the fiddly ones. The CommonJS TypeScript suite sets TS_NODE_PROJECT to a config in the test directory and registers ts-node. The ES module TypeScript suite does the same but also sets FASTIFY_AUTOLOAD_TYPESCRIPT=1, loads a register-ts-esm.mjs module, and runs with the node --import flag.
The CLI tests are separate from the template tests, and they cover the tool itself. There is unit:cli-js for the CommonJS test files, unit:cli-js-esm for the ES module ones, unit:cli-ts for the TypeScript ones, and unit:cli to run all three. The top level test script then runs the suites and c8 with clean for coverage, and the type checking is done separately with tstyche, which is a type test runner rather than a type checker, so the published type declarations are themselves tested.
The test script has a portability hack in it, a copy command for Windows followed by an rsync for Unix followed by an echo if this is fine, so the same script runs on both. The lint setup is eslint with a neostandard style badge in the README, and there is an .c8rc.json for the coverage configuration.
Where fastify-cli is the wrong choice
The first wrong case is a production deployment that does not want a global CLI. The tool is installed with npm install fastify-cli --global, and a service that depends on a globally installed package at startup is a dependency you do not control, on a machine whose global node_modules you did not provision. The eject command exists to solve this, and the answer for a serious deployment is to use it and not run the CLI in production.
The second is a project that wants its own tooling conventions. The generate command produces a project with a specific layout, a test setup and a package.json, and if your organisation has a house template, a shared config and a house test runner, the generated project is something you will immediately rewrite. At that point the value of the generator is thin.
The third is a codebase that does not use Fastify. The start command is specific to the Fastify plugin signature, and the print commands are specific to Fastify's radix tree router and its avvio plugin loader. There is no equivalent for Express or Koa here, and the tool is not a generic Node process manager.
The fourth is a monorepo with many small services. The CLI is a single project at the current working directory, and while the environment variable and config file options help with per-service settings, a workspace with dozens of services usually wants a per-service script rather than a global tool that reads the nearest configuration.
The alternative worth weighing is a plain node invocation with your own server file. Writing a small server entry that calls fastify.listen with your port and logger settings is a dozen lines, it has no global dependency, and it is what most production Fastify services do anyway. The CLI earns its keep in development, in scaffolding and in the two debugging print commands, rather than at runtime.
Editorial conclusion
Adopt fastify-cli if you build Fastify services and want a single tool to start a plugin during development, scaffold new projects and inspect the router when a route does not match what you expected. Keep away if your deployment already has its own process manager and you need a production start command that does not depend on a global install, since the tool is installed globally and the eject command exists precisely to remove that dependency. Before you rely on it, check the version you install, because the package is at 8.0.3 with releases on 2026-09-30, 2026-09-13 and 2026-09-08, and read the options table because every flag has a matching environment variable and the precedence between the config file and the command line is easy to get wrong.
Frequently asked questions
How do I install fastify-cli and start a Fastify plugin?
Install it globally with npm install fastify-cli --global, then run fastify start plugin.js. Running fastify with no arguments prints the help, and start is the default command, so fastify start plugin.js is the same as fastify with a file argument.
What are the print-routes and print-plugins commands for?
print-routes prints the representation of the internal radix tree used by the router, and print-plugins prints the representation of the internal plugin tree used by avvio. The README describes both as useful for debugging, since they expose structures that are otherwise invisible from application code.
How do I pass custom options to a plugin from the command line?
Add them after the -- terminator, for example fastify start plugin.js -- --one. If you also use the --options argument, the command line arguments take precedence. The plugin can also export its own options object, which the CLI passes through to the plugin's options argument.
What happens if a config file and a command line option set the same thing?
The command line argument wins. The README states that in case of a collision, where an argument exists in both the configuration file and as a command-line argument, the command-line argument is given the priority. Every option also has a matching environment variable.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fastify-fastify-cli)Was this useful?