Bootstrapping a Node App
In this tutorial, we will:
- Define some terms
- Install Node
- Create a barebones app
- Import code from another file
- Install and use a package
- Install and configure the Express web server
If you prefer video, scroll to the bottom. There’s a video there of me walking through the entire tutorial.
Some Definitions
JavaScript is a programming language, which has been around since 1996. It runs all over the place. We know it mainly from running in a browser (this is what it was invented for), then someone got the bright idea to make it run from the command line, and from that idea we got…
JavaScript Engine is term to describe the “container” that executes JavaScript. The two big ones are V8 from Google, and JavaScriptCore from Apple.
JavaScript Runtime “wraps” a JavaScript engine and lets it access the environment in which it runs.
[Note: you don’t need to worry much about differences between any of the above…]
Node.js (commonly just “Node”) is a JavaScript Runtime to execute JavaScript on a server. It was created in 2009. It’s the most common way to run JavaScript outside of a browser, but it’s the not the only way. Deno and Bun are two alternatives, for example, but Node is way more common than everything else combined.
npm is the “Node Package Manager” (it’s written in all lower case – I have no idea why). It’s a utility that helps you during development (not runtime – it does nothing when your app is actually running in production; it’s just a dev tool). It installs code, automates a bunch of stuff, etc. I don’t think you technically have to use npm, but it would be really painful not to. npm should install with Node – they’re pretty inseparable.
Express is a web server library for Node. It’s the most common library used to run a web app with Node.
Install Node
You can download an installer here. I won’t go into detail because the installation has always “just worked” for me, on Windows or Mac.
Once the installation is complete, do this from a command prompt (anywhere):
node -v
That command is running Node and asking it what version it is. If Node is installed, you should get something like this:
v20.10.0
(Your exact version might vary. It doesn’t matter.)
Bootstrap an App
Create an empty directory, named for your project. (The name doesn’t matter. I would avoid spaces because that can make some version control stuff hard…)
From a command prompt, enter the directory you just created, and do this:
npm init
The command prompt will ask you a bunch of questions – just keep hitting enter. Eventually it will exit, and it will have created a file in the directory called package.json
.
That’s literally all this command does – create this one file. It’s just JSON. Open it, look through it. This is just where Node and npm store and retrieve data about the app.
There’s nothing magic about the file – you can write it by hand if you want (hint: you don’t).
If you want to skip all the questions and just take the defaults, do this:
npm init -y
You can delete the newly-created file and recreate it if you want. Again, there’s nothing magic about it, and creating it is all the init
command does. (But after you’ve started doing stuff with your app, there will be more in this file, so don’t delete it then.)
Now create a folder called src
. This is where you will put your code. This is not required, but it’s a convention. Most Node developers will expect your code to be in src
.
Inside src
, create a text file called app.js
. This is the main executable file of your app. This is also not a required name, but it’s a loose convention. app.js
is common, but you might see server.js
or index.js
– it doesn’t really matter, honestly, so long as you know what it is.
Inside that file, type this:
console.log("Hello World!")
Now, from the root directory (one up from src
), do this from the command line:
node src/app.js
(Or, whatever you called your file, if you didn’t call it app.js
)
You should see:
Hello World!
Congratulations. This is a running Node app.
It’s self-contained – there’s nothing “outside” of this directory. You can move it or copy it and run it from some other location. You can even zip it up and email it to someone if you want – so long as they have Node installed, they can run it too.
Importing Code
You can do whatever you want inside app.js
, but eventually you’re going to want code from somewhere else.
Inside src
, create a directory called lib
(again, just a convention; call it whatever you want).
Inside that directory, create a file called greeting.js
.
Inside that file, do this:
function greeting() {
console.log("This is from your included code…");
}
export { greeting }
The first part of that is just a standard function. The last line is the important part – you have to explicitly specify everything you want available outside this file.
You can have a bunch of stuff in this file – you could have 1 million lines of code – but you have to specifically say what to want to be available outside of it when something else imports it. You might only export one little function call out of that million lines of code.
Now, go back to app.js
and write this at the top of that file:
import { greeting } from './lib/greeting.js'
What we’re saying there is that we want whatever greeting
is in the file located at ./lib/greeting.js
. We don’t know what it is – it might be a class definition, a function, a variable, it doesn’t matter. We just want to use it inside app.js
A few notes:
- The file location is relative to whatever is importing it.
- The file location has to begin with a dot:
.
This is so code knows you’re talking about a file include, and not an installed package. More on that below. - The
import
statements have to be at the top of the file. You’ll usually have a bunch of them up there. You can import inside code down below (to import code you might only use in specific circumstances), but that’s less common and we won’t cover it here.
Now that we’ve exported greeting
from our library code, and imported
it in our app code, we can use it. At the bottom of the app file, call the function:
greeting();
You should get output like this
Hello World!
This is from your included code…
So, we did two things:
- We exported
greeting
in our library file - We imported
greeting
in our app file
Obviously, the names have to match. If I try to import greetMe
, I’ll get this:
The requested module './lib/greeting.js' does
not provide an export named 'greetMe'
You can export multiple things at once. If you have another function in that file called otherGreeting
, you can do this at the bottom:
export { greeting, otherGreeting }
Then, at the top of your app file:
import { greeting, otherGreeting } from './lib/greeting.js'
You can export as many things as you like, but a general rule (in programming, universally) is that you only export what code outside the file should see. Expose as little as possible.
Also, if you don’t feel like writing an explicit export statement at the bottom, you can do it directly in the function declaration:
export function greeting()
Finally, if you only want to export one thing from a file, you can specify that one thing as the “default export,” like this:
export default greeting
What benefit does this provide over specifically giving it a name? Well, this means you don’t have to know the name of what you’re exporting, which can be handy when using other people’s code. You’re basically just saying, “Give me the main thing this code does…”
One effect this has is that the code using it can call the import anything it likes. And this makes sense: since there’s only one export, you don’t really have to know what it’s called and can call it whatever you want.
import wtf from './lib/greeting.js'
(Note that there are no braces around wtf
. Near as I can tell, this is how the engine knows if you want named exports or just want the default.)
Then you can use it as whatever you called it.
wtf()
Why we had to change package.json…
In a sidebar above, I had you add this to package.json
:
"type": "module"
This is because JavaScript changed how code was included, and it needs to know how to parse the file.
This was the old syntax:
const greeting = require('./lib/greeting.js');
Again, this old. But, for whatever reason, the newer syntax requires the package.json
change, and npm doesn’t add that by default.
I have no idea why. I assume this will change in the future, and you’ll have to modify package.json
to use the old syntax, but who knows…
Installing Packages
Eventually, at some point, you’re gonna want to use someone else’s code. For this example, we’re going to install and use a package called Moment.js which is used to format and manipulate dates (default date handling in JavaScript is very bad).
To install a package, do this from anywhere in your app.
npm install moment
This will download the code for the package, and put in a directory called node_modules
in the root of your application (so, one up from src
). npm installs packages to the nearest directory it can find that contains a package.json
file. So if you execute the install command in subdirectory, npm will keep looking “upward” until it finds the directory that contains package.json
.
If you go into node_modules
, you might find a bunch of stuff in addition to the package you installed. These are packages that package installed. These are called “transitive dependencies.” Sometimes, installing a single package will result in dozens of other packages being automatically installed because the package you want depends on them.
Once the package is installed, you import it pretty much just like we did previously.
import moment from 'moment'
The one difference here is that we’re not referring to a file, we’re referring to an installed package, so we don’t start with a dot (it’s not a file path, after all).
Also, moment has a default export, so we can call it whatever we want. In this case, we’re calling it moment
, which looks a little weird because the package is also called moment
. This is pretty common (we’ll do the same thing with Express below), but it always looks weird to me.
In your app file, do this:
console.log(moment().format())
moment
exports a function. We’re calling that function, then calling format()
on the output. This should give you output that looks something like this:
2025-02-09T20:44:29-06:00
So, other than the import
syntax changes, using installed packaged code is just like using included code of your own.
If you look in the package.json
, you’ll see that npm has modified it. It will have added something like this:
"dependencies": {
"moment": "^2.30.1"
}
So, now the app “remembers” that it has moment
installed and what version it is. Later, you can auto-upgrade or restore packages, because npm keeps track.
Package names sometimes will have @
signs in them. This is what those mean:
At
@
symbol at the beginning means the package is “namespaced.” An organization or person can name their package with their organization name in order to group them together and prevent naming conflicts. For instance, I could write a package and call it@deanebarker/mypackage
.At
@
later in the name means that what follows it is a version number. If I wanted to install a specific version of a package, I could specify that like this:npm install mypackage@2.0.0
You can remove a package like this:
npm uninstall moment
Installing and Running Express
In most cases, you’re going to want to run a web server. Node can and does a bunch of command line scripting stuff, but to write a Node web app, you need a web server.
Express is the most common web server for Node. (There are probably others, but I couldn’t name one…)
Install it:
npm install express
Now, in your app file, add this:
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(3000)
(Again, as with Moment, we’re using the package name for the name of the thing we’re importing, which just never looks right to me…)
We’re doing three things:
- Getting a variable called
app
from theexpress
function that we imported - Configuring a “route,” for the home page (just the forward slash). We’re supplying some code to send back a message
- Telling Express to listen on port 3000
Run this code like before, but you’ll see an important difference: the code does not finish. app.listen
doesn’t… stop. It will just hold the code there, while it listens for requests.
Open a browser and go to http://localhost:3000/
and you should see:
Hello World!
To stop the script, press CTRL-C
at your command line.
There’s a lot to Express that I won’t cover here, but I’ll finish by showing you how to map a variable route. Add this before the app.listen
line:
app.get('/greet/:name', (req, res) => {
res.send('Hello ' + req.params.name + '!')
})
(If you haven’t stopped the script from before, you’ll need to stop and restart it for these changes to work.)
This is mapping another route: /greet/[something]
. The code is grabbing the [something]
from the URL and using it in the message to the user.
Go to http://localhost:3000/greet/bob
and you should see:
Hello bob!
…and there you go. You wrote a functional web app in JavaScript, running under Node, using Express.
Video
Here’s a 15-minute video of me doing everything in this tutorial, complete with mistakes.