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 reassign

Callback 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 variable

Avoid 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 .gitignore file
  • Other developers run npm install to 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 use var)
  • Understand block scope vs function scope
  • Use package.json to manage dependencies
  • Never commit node_modules to git
  • We use Express to create web servers
  • Use path parameters for resource identification
  • Use query parameters for filtering and options