beginner

Creating NodeJS project


In this post we are going to cover how to create a proper project with npm init and how to use npm packages.

Creating a project

Until now we have created some .js files and our server does work but it is missing some important files and configurations. We are going to fix that now by going into our folder where our files are and just run the command npm init -y

Make sure that NodeJS (and with that, NPM) are installed before running above command. You can check that npm is installed by running npm -v. You should get version number.

After that, a package.json file will be created with content similar to this:

{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Most of it is self explanatory so let’s go through some parts that are not.

main - this is the main point of entry for the project, you can set it to any file.
scripts - this allows you to run any defined set of commands as a script. You can have as many as you want( we’ll create our own later).

Why is it important

package.json holds metadata configuration of your project. But probably the most important thing is that without it you can’t install npm packages as it is used to store definitions of those packages(dependencies).

Installing our first package

Now we are going to install our first dependency that will allow us to be able to read environment variables that are basically just values stored in a file .env (. is very important as a prefix of the file). This file needs to be in the same folder as other files otherwise it will not work.

So first we want to create that file and inside we want to put:

PORT=3000

Now we can run the command to install our package npm install dotenv. After this, dependencies object will be added to our package.json and it will look similar to this:

{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^16.3.1"
  }
}

dependencies - this hold all of our installed packages that are used in production and their versions. There is also devDependencies that we will add later. These are used for installed packages that are used in development and testing and are not installed for production.

You will also see file package-lock.json that keeps track of dependency trees for all dependencies you installed. You don’t have to worry much about this file. There is also a folder node_modules which holds all installed packages.

The version installed for me is 16.3.1 and that might not be the case for you because as times goes on developers of these packages release new versions and depending when you install it you will probably get different/newer version.

What does ^16.3.1 mean?

^ - allows the project to automatically update dependency to future minor/patch versions, without incrementing the major version. Sometimes you’ll see ~ that does the same thing but only updates minor version and not major or patch versions. There are a few more values available.
16 - represents the major version of the package. New major versions almost always have changes that break backward compatibility. This should only be updated manually.
3 - minor version. Backward compatible new features. This can be updated automatically.
1 - patch version. Backward compatible bug fixes. This can be updated automatically.

More about it here.

Changing our code

Now that our package is installed and our .env file exists and has a variable inside we can change our Ignitor.js.

Ignitor.js

const http = require("http");
const Route = require("./Route.js");
require("dotenv").config();

class Ignitor {
  #instance;
  #route;
  constructor() {
    if (this.#instance) {
      throw new Error("New instance cannot be created.");
    }

    this.#instance = this;
  }

  setup() {
    this.#route = Route;
  }

  start() {
    this.setup();
    http
      .createServer((req, res) => {
        res.setHeader("Content-Type", "application/json");
        return this.#route.handle({ req, res });
      })
      .listen(process.env.PORT);
  }
}

module.exports = new Ignitor();

We are first importing our package with require('dotenv').config();. This loads our key-value pairs from .env into process.env. That allows us to remove PORT constant from our code and instead use .listen(process.env.PORT);

Now if we run node index.js everything works as before.

Creating our script

To make things easier for us, we are going to create our own script to run node index.js and its just updating our package.json

package.json

{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^16.3.1"
  }
}

Now we only need to run npm run start and it will actually run our node index.js as defined in the file.

Folder structure

index.js
routes.js
Route.js
Ignitor.js
package.json
package-lock.json
.env
node_modules(folder)
Previous Creating proper routes