DEV Community

Cover image for Create New Project Using NPM package
Dantis Mai
Dantis Mai

Posted on • Updated on


Create New Project Using NPM package

In the last article, we can create a new project by using Express Typescript Boilerplate template, it saves us pretty much time to do it from scratch. However, it is still not the best, we can optimize it using NPM and just 1 command line, we get everything.

Setup account

  • we will need an account in npm
  • Login our account on our pc
  • After that, we update our profile by access profile > edit profile. There are 2 things we need to pay attention GitHub Username and Email, they will cause some problems when publishing a package. For example, after running command npm publish, it returns 403 Forbidden - You do not have permission to publish. Are you logged in as the correct user?. In case that we can fix it by changing email to whatever, then revert it back to our main email.

Setup project

If we config npm account successfully, just running npm publish, then we will see the log below
publishing successfully

Then it will be shown on our npm packages, and the other developer can reach to the package also
Image description

In order to make our package more reliable, we should enable security. If there is any issue Github will show us as below.
security issue

Otherwise, it will be green.
green security

Config command

In this post, we will use our last template as source code for the npm package. And below is my package.json.

  "name": "typescript-maker",
  "version": "1.1.1",
  "description": "Minimalistic boilerplate to quick-start Node.js development in TypeScript.",
  "engines": {
    "node": ">=14 <15"
  "bin": {
    "typescript-maker": "./bin/generateApp.js"
  "scripts": {
    "start": "node src/index",
    "dev": "nodemon --config restart.json",
    "clean": "rm -rf coverage build tmp",
    "prebuild": "npm run lint",
    "build": "tsc -p",
    "build:watch": "tsc -w -p",
    "lint": "eslint . --ext .ts,.tsx",
    "test": "jest"
  "author": "Dantis Mai",
  "license": "Apache-2.0",
  "dependencies": {
    "commander": "^8.3.0",
    "express": "^4.17.1",
    "module-alias": "^2.2.2",
    "tslib": "~2.3.0",
    "winston": "^3.3.3"
  "devDependencies": {
    "@tsconfig/recommended": "^1.0.1",
    "@types/express": "^4.17.13",
    "@types/jest": "^26.0.24",
    "@types/node": "~14.14.45",
    "@typescript-eslint/eslint-plugin": "~4.28.2",
    "@typescript-eslint/parser": "~4.28.2",
    "dotenv": "^10.0.0",
    "eslint": "~7.30.0",
    "eslint-config-prettier": "~8.3.0",
    "eslint-plugin-jest": "~24.3.6",
    "jest": "^27.0.6",
    "jest-html-reporter": "^3.4.1",
    "nodemon": "^2.0.12",
    "prettier": "~2.3.2",
    "rimraf": "^3.0.2",
    "supertest": "^6.1.5",
    "ts-jest": "^27.0.3",
    "ts-node": "^10.2.0",
    "ts-node-dev": "^1.1.8",
    "tsconfig-paths": "^3.10.1",
    "tsutils": "~3.21.0",
    "typescript": "~4.3.5"
Enter fullscreen mode Exit fullscreen mode

In the package.json file, there are 3 fields, we need to update:

  • name: npm package name. This name will be our npm package name, ignoring the GitHub repository name. For example, my repository name is express-typescript-boilerplate, while the package name is typescript-maker.
  • version: npm package version. By versioning, we can update the new features with backward compatibility.
  • bin: command configuration. We will direct the source code for the command. As you can see the bin field in my package.json, I define "typescript-maker": "./bin/generateApp.js", it means that typescript-maker is the command name, and the options and arguments are described in ./bin/generateApp.js.

Now, let jump to config our command.
For a sample command, there are 4 steps:

  • Verify arguments: verify the number of arguments to make sure, we have enough information.
  • Parse arguments and options: get argument value from input
  • Validate existing folder: prevent issues when creating a new folder or file. It will work like we clone a repository 2 times at the same directory.
  • Define workflow: define what thing we gonna do when we call the command.
  • Clear unused files: keep the result clean to not distract the user after running the command.
  • Trigger workflow.

Combine everything and we have a sample config for typescript-maker below

# Verify arguments
if (process.argv.length < 3) {
  console.log('You have to provide a name to your app.');
  console.log('For example :');
  console.log('    typescript-maker my-app');

# Parse arguments and option
const projectName = process.argv[2];
const currentPath = process.cwd();
const projectPath = path.join(currentPath, projectName);
const git_repo = '';

# Validate existing folder
try {
} catch (err) {
  if (err.code === 'EEXIST') {
    console.log('Directory already exists. Please choose another name for the project.');
  } else {

# define steps in workflow
async function main() {
  try {
    console.log('Downloading files...');
    execSync(`git clone --depth 1 ${git_repo} ${projectPath}`);

    // Change directory

    // Install dependencies
    const useYarn = await hasYarn();
    console.log('Installing dependencies...');
    if (useYarn) {
      await runCmd('yarn install');
    } else {
      await runCmd('npm install');
    console.log('Dependencies installed successfully.');

    # Clean unused files
    console.log('Removing useless files');
    execSync('npx rimraf ./.git');
    fs.rmdirSync(path.join(projectPath, 'bin'), { recursive: true});

    console.log('The installation is done, this is ready to use !');

  } catch (error) {

# trigger workflow
Enter fullscreen mode Exit fullscreen mode

In case we want a more complicated command, we can use module commander, which supports us pretty much thing when we design the architecture of the command. After using the commander, I structure my command like this.

new project

This is mine, you can enjoy it.


I am really happy to receive your feedback on this article. Thanks for your precious time reading this.

Top comments (3)

johndeighan profile image
John Deighan

Also: path is not defined (I use import path from 'node:path';) and 'error' is not defined (in error handling, you're using both 'err' and 'error' - they should be the same).

johndeighan profile image
John Deighan

This post clears up quite a bit that I didn't understand about creating an app template. For example, I thought using 'npm create ???' would automatically clone the repo. However, I see several problems in your code. It looks like JavaScript, but uses '#' style comments. Also, you're creating the director appPath, but there's no such variable. I think you meant projectPath???

johndeighan profile image
John Deighan

Also, fs is not defined. Use import * as fs from 'node:fs';