How to build an npx starter template
Our favourite frameworks have starter templates to help us get started with setting up our projects with minimal configuration. Some known examples:
- Create React App (
npx create-react-app my-app
) - Next.js (
npx create-next-app@latest
) - Remix (
npx create-remix@latest
)
I have created a sample starter template that I will walk through in this article.
How they work: these npm packages have an executable file configured in the package.json
:
{
"name": "create-my-template",
"version": "1.0.0",
"bin": {
"create-my-template": "./index.js"
}
}
When the user installs the npm package, it will also install the executable configured in bin
, allowing the user to run the executable:
$ npm install create-my-template
$ create-my-template
npx
is part of npm
: it allows you to run a command from an npm package. If the package has a single bin
script configured, that command will be used. npx
will also fetch the package remotely if it is not installed locally.
This means users run the below command and npx
will download the package and run the executable in one line:
$ npx create-my-template
Now that we can execute a command, let’s write up an installation script that will generate a starter template. A basic script can look like the one below:
#!/usr/bin/env node
// Usage: npx create-my-template my-app
const spawn = require('cross-spawn');
const fs = require('fs');
const path = require('path');
// The first argument will be the project name.
const projectName = process.argv[2];
// Create a project directory with the project name.
const currentDir = process.cwd();
const projectDir = path.resolve(currentDir, projectName);
fs.mkdirSync(projectDir, { recursive: true });
// A common approach to building a starter template is to
// create a `template` folder which will house the template
// and the files we want to create.
const templateDir = path.resolve(__dirname, 'template');
fs.cpSync(templateDir, projectDir, { recursive: true });
// It is good practice to have dotfiles stored in the
// template without the dot (so they do not get picked
// up by the starter template repository). We can rename
// the dotfiles after we have copied them over to the
// new project directory.
fs.renameSync(
path.join(projectDir, 'gitignore'),
path.join(projectDir, '.gitignore')
);
const projectPackageJson = require(path.join(projectDir, 'package.json'));
// Update the project's package.json with the new project name
projectPackageJson.name = projectName;
fs.writeFileSync(
path.join(projectDir, 'package.json'),
JSON.stringify(projectPackageJson, null, 2)
);
// Run `npm install` in the project directory to install
// the dependencies. We are using a third-party library
// called `cross-spawn` for cross-platform support.
// (Node has issues spawning child processes in Windows).
spawn.sync('npm', ['install'], { stdio: 'inherit' });
console.log('Success! Your new project is ready.');
console.log(`Created ${projectName} at ${projectDir}`);
This is a basic example. Other things you will need to consider when you build your own:
- You can use tools like commander or inquirer to add interactive command-line interface.
- Add some error-handling. Examples: Does the project directory already exists before attempting to create it? Did the user specify the project name?
- Check which package manager the user prefers to use (eg pnpm, yarn).
- Add some styling using chalk.
- Do you want to initialize a git repository in the project directory?
- Should the installation script do a clean up if installation failed?
Working on a monorepo? This approach can also be used to create packages in your monorepo from a template!
Once you’re happy with your starter template, you can npm publish for everyone to use.
Source code examples
A great way to learn is by reading the source code of other starter templates that are known across the community: