What On Earth Are 'package.json' And 'package-lock.json' Files?
Understand the significance of 'package.json' and 'package-lock.json' through this blog post!
Hola Developers!
I am sure you have come across the files 'package.json' and 'package-lock.json' before, right? Many of you might even be using them daily. I have been, too, for building my projects, but I have been guilty of not understanding what exactly these files do and what problems they solve. So today, we will try to understand the significance of the files package.json and package-lock.json and why they are so crucial for our project, as always, in simple words :)
What Is package.json?
package.json is a configuration file for our npm. It is a json file that lists all our installed dependencies and scripts and relevant meta-data about our project. When someone clones your repository from GitHub onto their local machine and runs the command npm install
, all the dependencies with their mentioned versions (this in itself is a little bit nuanced, which we will get to in a bit) will be installed from the huge npm repository onto your local machine.
How Do You Create A package.json File?
The package.json file is typically located in the root directory of a Node.js project and is automatically generated when you run the npm init
command to initialize a new project. You can also run the command npm init -y
to initialize a package.json file with default values. You can, of course, change these values later.
A typical package.json file looks like this:
{
"name": "quizzify",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node backend/server.js",
"server": "nodemon backend/server.js",
"client": "npm start --prefix frontend",
"dev": "concurrently \"npm run server\" \"npm run client\" ",
"data:import": "node backend/seeder.js",
"data:destroy": "node backend/seeder.js -d"
},
"keywords": ["nodejs", "react.js", "react-bootstrap"],
"author": "Jyothi Swaroop",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"colors": "^1.4.0",
"cookie-parser": "^1.4.6",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^7.5.2"
},
"devDependencies": {
"concurrently": "^8.2.1",
"dotenv": "^16.3.1",
"nodemon": "^3.0.1"
}
}
As you can see, it is a json file. And if this feels intimidating to you right now, don't worry. Let's go through all the properties one by one :)
"name": The name field defines the name of the package. It must be lowercase and a single word. It can be up to 214 characters long. It can contain underscores and hyphens but not spaces and other special characters. Having a name for the file is mandatory.
"type": Now, this property does not come with the default set of properties when you first initialize a package.json file. My frontend was configured with the import syntax, and in the backend, I wanted to keep this syntax consistent. So, instead of using the 'require' syntax, i.e., common JS syntax, I specified the type: module in package.json to allow me to use the import syntax in my backend.
"version": It describes the current version of json being used in the program. This is also an essential field in case of publishing the file.
"description": A useful property that describes what this package is about.
"main": This property specifies the entry point or the 'root' of your application. Typically, you would name it index.js, but as you can see, I named it 'server.js'.
"scripts": These are terminal commands that can be run with the command
npm run <scriptName>
."keywords": The keywords most related to your project, which have a high chance of being searched by users, are listed in this row.
"author": Here, you can specify your name as the author name of the project.
"license": 'MIT' and 'ISC' are the universally accepted licenses. As a beginner, knowing this much is enough.
"dependencies": The most important property. When we install a package via
npm install
, it gets added to this dependency object, along with the version number that was installed. Now, here you will come across two symbols^
and~
. Understanding what they mean is extremely crucial as they can lead to version conflicts.^
: A typical version number has three distinct parts. For example, in version 2.8.3, 2 signifies a major upgrade, 8 signifies a minor upgrade, and 3 signifies the latest patch updates for this particular version. So, if in package.json, a dependency is installed with the^
sign, like^2.8.3
, then that means it will only install minor upgrades when new upgrades are available for that particular dependency.~
: But, if a dependency is installed with the~
sign, like~2.8.3
, then it will install the major upgrades as well as the minor upgrades If available. Now, this is dangerous as major upgrades can break your code, as they come with new features. Meanwhile, minor patch updates are safer as they most often signify bug fixes.
When we install a particular dependency, all the necessary files of this dependency will be installed in a folder called 'node_modules' in your project directory. If you open this folder, you will see that it is quite HUGE!
Ever wondered why node_modules are so HUGE?
Let us say we install the package 'parcel'. Parcel, as an independent dependency, has its own dependencies (files and packages it is dependent on), which have their own dependencies, and so on. These are called 'transitive dependencies'. This causes the node_modules folder to bloat up and hence takes up a lot of memory. There are many funny memes on node_modules. I found this one particularly funny :)
P.S. I hope you got the reference!
Should we push the node_modules folder onto GitHub?
Anything that can be regenerated, there's no need to push it onto GitHub. The developer who wants to work with your existing project simply needs to clone your repository and then run npm install, which would fetch all the installed dependencies. Now, package.json did not provide a way to lock down the specific version of each dependency that a project was using. We will get to this in just a second.
"devDependencies": Any dependency that you only need during the development phase and not the production phase, you install it as a devDependency.
package-lock.json
As we were just talking, package.json did not provide a way to lock down the specific version of each dependency that a project was using. This meant that when a project was deployed or shared with others, there was a risk that different developers or machines would use different versions of the same dependency, which could cause compatibility issues or unexpected behaviour.
Thus came package-lock.json to our rescue! This file keeps track of the exact version of the dependencies and all the transitive dependencies our application uses. Meanwhile, package.json can have
~
and^
.The package-lock.json file is created when you run npm install in your directory. You must push this file along with package.json to your version control system. So, package-lock.json essentially tackles the problem of version conflicts, ensuring that the same dependencies are installed consistently across different environments, such as development and production environments. A typical package-lock.json file will also be quite lengthy, as it keeps track of each and every dependency our project is using.
Conclusion
And that's a wrap! I hope after reading this blog post, you have a good understanding of what these files are and their significance! But before leaving, I have one quick question for you:
How many package.json and package-lock.json files does your project contain? Is it equal to the number of package.json and package-lock.json files you create manually, or is it something else? Let me know in the comments below! I am waiting for your response :)
I hope you found this blog helpful. Until next time.
Adios!
~ Jyothi Swaroop Makena