Every Node.js project you encounter, whether AI-generated or hand-written, starts with a package.json file. It is the blueprint of the project. When AI scaffolds a project for you, it creates this file, fills in dependencies, and writes npm scripts. Knowing how to read and evaluate what AI puts in package.json is a fundamental skill, because AI makes mistakes here that can bloat your project, introduce security vulnerabilities, or break your builds.
What npm does
npm (Node Package Manager) does three things: it installs packages from the npm registryWhat is registry?A server that stores and distributes packages or container images - npm registry for JavaScript packages, Docker Hub for container images. (a massive online database of JavaScript packages), it manages which packages your project depends on, and it runs scripts you define in package.json.
# Install all dependencies listed in package.json
npm install
# Add a new dependency
npm install express
# Add a dev dependency
npm install --save-dev typescript
# Run a script defined in package.json
npm run build
# Shorthand for common scripts
npm start # runs "start" script
npm test # runs "test" script| Command | What it does | When to use it |
|---|---|---|
npm install | Installs all deps from package.json | After cloning a project, or after package.json changes |
npm install <pkg> | Adds a package to dependencies | When your app needs a library at runtime |
npm install -D <pkg> | Adds a package to devDependencies | Testing tools, linters, build tools |
npm uninstall <pkg> | Removes a package | When you no longer need a dependency |
npm run <script> | Executes a script from package.json | Building, testing, starting dev servers |
npm outdated | Shows which packages have newer versions | Periodic dependency maintenance |
Anatomy of package.jsonWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it.
Here is a typical package.json with the fields you will see most often:
{
"name": "my-api-server",
"version": "1.0.0",
"description": "REST API for the dashboard app",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"build": "tsc",
"test": "vitest"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3"
},
"devDependencies": {
"nodemon": "^3.0.2",
"typescript": "^5.3.3",
"vitest": "^1.2.0"
}
}The key fields
name: The project name. Lowercase, no spaces. Must be unique if you publish to npm.
version: The current version, following semver (more on this below).
type: Either "module" (ES ModulesWhat is es modules?The official JavaScript module standard using import and export - enabled in Node.js via "type": "module" in package.json., import/export) or "commonjs" (the default, require/module.exports). This single field controls how Node.js interprets your .js files. We cover this in the next lesson.
scripts: Custom commands you run with npm run <name>. This is where you define how to start, build, test, and deploy your project. start and test are special, they do not need the run keyword.
dependencies: Packages your application needs to run in production. Express, database drivers, authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. libraries.
devDependencies: Packages needed only during development. Testing frameworks, TypeScript compilerWhat is compiler?A program that translates code you write into a language your computer can execute. It also catches errors before your code runs., linters, build tools.
dependencies instead of devDependencies means your production Docker image installs the TypeScript compiler even though the code is already compiled. A testing framework in dependencies adds unnecessary weight to production. Always review which section AI places packages in.Semver: reading version numbers
Every package version follows semantic versioningWhat is semantic versioning?A numbering system (major.minor.patch) that communicates whether a release contains breaking changes, new features, or bug fixes.: MAJOR.MINOR.PATCH.
| Part | When it changes | Example |
|---|---|---|
| MAJOR (4.x.x) | Breaking changes, your code might break on update | Express 3 to Express 4 |
| MINOR (4.18.x) | New features, backward-compatible | New middleware added |
| PATCH (4.18.2) | Bug fixes, backward-compatible | Security patch |
The ^ and ~ prefixes in package.jsonWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. control which updates npm installs automatically:
"^4.18.2", Allows minor and patch updates (4.18.2 through 4.x.x, but not 5.0.0)"~4.18.2", Allows only patch updates (4.18.2 through 4.18.x)"4.18.2", Exact version, no updates
The ^ prefix is the default when you run npm install. It is usually the right choice, you get bug fixes and new features without breaking changes. But "backward-compatible" is a promiseWhat is promise?An object that represents a value you don't have yet but will get in the future, letting your code keep running while it waits., and sometimes packages break it.
The lock file
package-lock.json records the exact version of every package that was installed, including all sub-dependencies. When you run npm install, npm uses the lock file to install the exact same versions every time.
package.json says: "express": "^4.18.2"
package-lock.json says: "express": "4.18.3" (the exact resolved version)Always commitWhat is commit?A permanent snapshot of your staged changes saved in Git's history, identified by a unique hash and accompanied by a message describing what changed. package-lock.json to git. Without it, two developers running npm install on the same project can get different package versions, leading to "it works on my machine" bugs.
| File | Purpose | Commit to git? |
|---|---|---|
package.json | Declares dependency ranges | Yes |
package-lock.json | Locks exact versions | Yes |
node_modules/ | The actual installed packages | No (add to .gitignore) |
node_modules: the elephant in the room
When you run npm install, npm downloads every package and its dependencies into a node_modules folder. A modest project can easily have 500+ packages in node_modules, totaling hundreds of megabytes.
This is normal. You do not check node_modules into git, anyone who clones the project runs npm install to recreate it from package.json and the lock file.
npm install, ask: "Can I do this with what I already have?" Often, the answer is yes.npm scripts in practice
Scripts are the command center of your project. Here are common patterns:
{
"scripts": {
"dev": "nodemon --watch src src/index.ts",
"build": "tsc && node dist/index.js",
"start": "node dist/index.js",
"test": "vitest",
"lint": "eslint src/",
"prestart": "npm run build",
"posttest": "npm run lint"
}
}The pre and post prefixes are special, prestart runs automatically before start, and posttest runs after test. This lets you chain operations without complex shell commands.
Evaluating AI-generated package.jsonWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it.
When AI generates a package.json for you, check these things:
- Are the packages current? AI training data has a cutoff. It might suggest Express 4 when Express 5 is available, or a package that has been deprecated.
- Are there unnecessary packages? If the project has
lodashbut only uses one function from it, that is bloat. - Are dev dependencies separated correctly? Testing and build tools belong in
devDependencies. - Does the lock file exist? If AI generates a project without
package-lock.json, runnpm installto create one. - Are the scripts reasonable? A
devscript should use a file watcher. Abuildscript should compile TypeScript if the project uses it.