First Server
Variables, functions and callbacks in JavaScript, npm package management, and setting up a first Node.js server with Express.
This week we dive deeper into Javascript, we learn more about variables, functions in general, but also callback functions and how to use them. We also look at how to setup a Node.js server, so that we can implement our first REST API server.
Advanced JavaScript
Functions
The traditional way to define a function in Javascript:
function getRandomInt(min, max){
return Math.floor(
Math.random() * (max + 1 - min) + min
);
}Hoisting
Function declarations are 'hoisted' to the top of their scope, meaning you can call them before they are defined in your code:
console.log(getRandomInt(4,8))
function getRandomInt(min, max){
return Math.floor(
Math.random() * (max + 1 - min) + min
);
}Anonymous Functions
Functions without a name, assigned to a variable:
const getRandomIntAnonymousFunction =
function (min, max) {
return Math.floor(
Math.random() * (max + 1 - min) + min
);
}
This will prevent accidental reassignment - when using const, you ensure
the function reference can't be changed.
const myFunc = function() { return 'original'; };
myFunc = function() { return "new"; };
// Error! Can't reassignCallback Functions
A callback function is a function passed as an argument to another function:
function genericActionPerformer(name, action) {
return action(name);
}
function eatingAction(name) {
return `${name} is eating`;
}
console.log(
genericActionPerformer("Valdemar", eatingAction)
);
// Output: "Valdemar is eating"
We can also pass an anonymous arrow function directly as the callback:
const runningAction = (name) => {
return `${name} is running`;
};
console.log(
genericActionPerformer("Sidi", runningAction)
);
// Output: "Sidi is running"Inline arrow function callbacks
console.log(genericActionPerformer(
"Kristian",
(name) => `${name} is laughing`
));
// Output: "Kristian is laughing"
Callbacks are fundamental in Node.js for handling asynchronous operations, event handling, and HTTP requests.
Scoping (const, let, var)
How and where a variable can be accessed depends on how it was declared.
The problem with var
Never use var in modern JavaScript because of this problematic scoping behavior:
{
var someVariable = true;
{
var someVariable = false;
}
// false. inner block overwrites the outer!
console.log(someVariable)
}
var is function scoped, not block-scoped:
// This prints 5 six times (bug!)
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}Block scope with let
let is block-scoped, which means that variables are contained within {}:
{
let someVariable = true;
{
let someVariable = false; // Different variable
}
// true - each block has its own variable
console.log(someVariable);
}
Always use const unless you know the variable will be reassigned:
const name = "Valdemar";
// name = "Niko";
// Error: Assignment to constant variableAvoid Global Variables
Never declare variables without const, let or var:
// DON'T DO THIS!
totalGlobalVariable = "";
// Creates global variable (bad!)Reserved Keywords
Some words are reserved and cannot be used as variable names. Attempting to do so will throw a syntax error.
break, case, catch, class, const,
continue, debugger, default, delete,
do, else, export, extends, finally,
for, function, if, import, in,
instanceof, new, return, super,
switch, this, throw, try, typeof,
var, void, while, with, yield
In strict mode:
let, static, implements, interface,
package, private, protected, public
Package Management
NPM and Package.json
NPM (Node Package Manager) is the default package manager for Node.js. It allows you to install third-party libraries (packages), manage project dependencies, run scripts and share your own packages.
package.json
This file defines your project and its dependencies. It contains metadata about your project:
{
"dependencies": {
"express": "5.2.1"
},
"devDependencies": {
"eslint": "^9.39.2"
}
}
Dependencies are packages required for your application to run in production, while DevDependencies are only needed during development, such as testing, linting and build tools.
"dependencies": {
"express": "5.2.1"
}"devDependencies": {
"eslint": "^9.39.2"
}Installing packages
# Install a dependency
npm install express
# Install a dev dependency
npm install --save-dev eslint
# Install a global dependency
npm install -g nodemon
# Install all dependencies from package.json
npm install
Global packages are command-line tools available across all projects, rather than libraries imported into a specific codebase. Nodemon is a good example, installed once and used from the terminal in any project.
Version Numbers
NPM uses semantic versioning (semver):
"express": "5.2.1"
│ │ │
│ │ └─ Patch (bug fixes)
│ └─── Minor (new features)
└───── Major (breaking changes)
5.2.1: Exact version^5.2.1: Compatible with 5.x.x (allows minor and patch updates)~5.2.1: Compatible with 5.2.x (allows only patch updates)
Node_modules
When you run npm install, NPM downloads all packages and their dependencies into a
folder called node_modules.
- Never commit node_modules to Git: it's huge and unnecessary
- Add
node_modules/to your.gitignorefile - Other developers run
npm installto get the same dependencies
How It Works
your-project/
├── node_modules/ # All installed packages
│ # (auto-generated)
│ ├── express/
│ ├── eslint/
│ └── ... (hundreds of dependencies)
├── package.json # Your dependency list
└── package-lock.json # Exact versions installed
When you require() or import a package, Node.js looks for it in node_modules:
// Loads from node_modules/express
const express = require('express');
Express.js
Express is a minimal and flexible Node.js web application framework that provides a way of doing simple routing, HTTP request/response handling and easy server setup.
Server Setup
// Import express
const express = require('express');
// Create an Express application
const app = express();
// Define routes (covered below)
// Start the server on port 8080
app.listen(8080);
You now have a server running at http://localhost:8080
GET Requests
GET is the default HTTP method, used to retrieve data from the server without modifying anything.
Basic Route
app.get('/', (req, res) => {
res.send({ data: 'Welcome to my server!' });
});
app.get(): Defines a GET route'/': The endpoint/path(req, res) => {}: Callback function (request, response)res.send(): Sends a response to the client
app.get('/snowstorm', (req, res) => {
res.send({ data: 'Snowstorm at 12!' });
});
Visit http://localhost:8080/snowstorm to see this response.
Path Parameters
Use :paramName in the route to capture dynamic values from the URL:
app.get('/cars/:carModel', (req, res) => {
console.log(req.params); // { carModel: 'tesla' }
res.send({
data:
`Your ${req.params.carModel} is very nice`
});
});
URL: http://localhost:8080/cars/tesla
Response: { data: "Your tesla is very nice" }
Multiple path parameters:
app.get('/cars/:carModel/:year', (req, res) => {
// { carModel: 'tesla', year: '2024' }
console.log(req.params);
res.send({
data: `Your ${req.params.carModel}
from the year ${req.params.year}
is very nice`
});
});
URL: http://localhost:8080/cars/tesla/2024
Response: { data: "Your tesla from the year 2024 is very nice" }
Query Parameters
Use req.query to access parameters from the URL query string:
app.get('/bag', (req, res) => {
res.send({ data: req.query });
});
URL: http://localhost:8080/bag?color=red&size=large
Response: { data: { color: "red", size: "large" } }
Summary
- Use arrow functions and callbacks for flexible, functional code
- Always use
const, unless you know the value will change (never, ever usevar) - Understand block scope vs function scope
- Use
package.jsonto manage dependencies - Never commit
node_modulesto git - We use Express to create web servers
- Use path parameters for resource identification
- Use query parameters for filtering and options