First Servers

About Node.js

  • We're going to use Node.js (docs) (handy guide)
    • with Express.js - a server framework, to make it easier...
  • Node itself is a shell script that runs a process that creates an environment and runs javascript in it (allowing us to create servers!)
  • Even numbers get Long term support (LTS)
  • Use nvm (node version mananger) to run different version on your machine
  • Let's look at processes...

Node Processes

(& Environment Variables)

  • To start a process type node and hit return
  • To see the process details type process (handling guide)
  • The process gets some environment variables, which are meant for use in programming. These are visible in process.env
    • They are often used to hold settings and secrets
    • They are just bash variables, so you can add them inline in your commands NAME=fred npm start or via dotenv (guide)
  • There are some injected variables that can show where you are on the disk __filename and __dirname 
  • (In React/Vite: https://vitejs.dev/guide/env-and-mode.html)

A word on modules:

  • Node, as of version 13.2.0, now supports es modules natively (docs)
    • Either with a short workaround 'Michael Jackson' files (.mjs)
    • or by putting "type": "module" in the package.json
  • Up until now, node has used the 'common.js' format, which is shown in these slides:
// For importing: import package from 'package' becomes
const package = require('packageName or path');
// can be deconstructed, like: import { partpackage } from 'package'
const { subPackage } = require('packageName or path');
                                  
                                  
// For exporting: export default becomes 
module.exports = something; //
// export <something> becomes 
exports.something1 = something1;
exports.something2 = something2;
// Unlike es6, you can't use module.exports AND exports.<thing> together
  • As referenced here
  • You can provide a package name or filepath

Node Built-in Modules

  • In node, the global namespace (like window) is global[This]
  • Node has some useful built-in modules you can just require:
    • os - tells you about the operating system and current user
    • fs - lets you read/write to the file system - this is something you mostly can't do with client-side code.
    • path - helps you normalise paths
    • process - lets you monitor/interact with the process
    • child_process - lets you launch another process for running shell commands
    • util - full of utilities, like inspect and promisify, types, etc.
    • Events 
    • Stream
    • http
    • etc.

Procedure for starting the project

  • Use your command line
  • Create a new folder in a relevant place and move into it
  • git init to turn it into a git repo
  • npm init to get package management
  • git add -A && git commit -m "npm initialised"
  • npm i express body-parser
  • Create a public folder, which contains HTML, CSS & JS
  • Create a server.js file which we'll run to start the server
    • later we'll split these into index.js (which starts the server) and app.js which holds the actual code for it
      • This is so it can be started in testing

Express JS

...but we'll do it manually first

Creating a basic server

const express = require('express');
const app = express();

// Good Practice, see next slide
const PORT = process.env.PORT || 3333;

// This starts the server
// Later this part goes in index.js
app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});
  • Get the express software
  • execute it to return a working server
  • set the server to listen on a port (3333)
  • and log a message when it is listening

To run this file, go to your command line and do:

npm start

(in your server.js)

Ports

  • Servers use ports to control traffic
  • Ports are gates to the outside world
  • A server process must bind to a port in order to hear requests sent [to that port]
  • Some ports well-known (aka 'system ports'), such as 80, 8080, 8008. (http), 443 (https), etc. (0 to 1023)
    • Avoid them
    • It's fine on yours to use numbers like 3000, 3333, etc. 'Registered ports' aren't a problem on your local
    • ...and when you put your software on a 3rd-party server, like heroku, you'll get given a port via process.env.PORT
  • Sometimes another program may be blocking your port (EACCESS error). Check it's not your old node server running.
    • Sometimes processes go 'stale' and hang around
    • See here to find out what it is and kill it if necessary

Creating a file server

const express = require('express');
const app = express();

// Good Practice
const PORT = process.env.PORT || 3333;

app.use(express.static('public'));

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});
  • express.static is a 'middleware' function that allows static file serving
  • Create some html files in the public directory and then request them from localhost:3333/<myFile>.html

Running Scripts

  • The shell command node has a second parameter which is a path, e.g.: node index.js will run the index.js script
  • Usually, in a project, what is run is controlled by the npm start command
    • By default, this command runs node server.js
    • If you want to run another script you'll have to add a "start" script to the "scripts" block
  • When we run the node process we kill it with ctrl + c
  • When you run a process it loads and runs your script and uses it in that process forevermore. If you make changes to your script (not forgetting to save!) you need to do the equivalent of refreshing the browser, which is to restart the server
  • Doing that manually is repetitive and boring, so we use a helper program called nodemon, which we install as a devDependency
    • default nodemon command is node index.js

Middleware

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3333;

app.use(function myLogger(req, res, next) {
  console.log('LOGGED')
  next(); // passes the request on to next middleware
});

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});
  • Middlewares are helper fns/enhancements (guide to writing)
    • ​Things like which check security or collate information
  • app.use(); loads middleware into an array
  • A request travels through that array in the order loaded
  • Just like route handlers, middlewares get passed a request and response object, and a next function (which passes to next mw)
    • Often shortened to (req, res, next)

Middleware: Route Handlers

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3333;

app.use(express.static('public'));

const cars = [{name: 'ferarri'}, {name: 'lamborghini'}];

app.get('/cars', function(req, res){
    return res.json(cars);
});

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});
  • Route handlers act like user interaction events (click, mouseover, etc.) in the browser
  • a GET request aimed at a certain URL (e.g. /cars) will run the handler function
  • As mentioned, you get two 3 arguments passed to a hander

Getting the request body

const express = require("express");
const app = express();
const PORT = process.env.PORT || 3333;

app.use(express.static("public"));

// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }));
app.use(express.json()); // parse application/json

const cars = [{ name: "ferarri" }, { name: "lamborghini" }]; // In memory

app.get("/cars", function(req, res) {
  return res.json(cars);
});

app.post("/cars", function(req, res) {
  cars.push(req.body);
  return res.sendStatus(201);
});

// ....Delete and Update go here

app.listen(PORT, function() {
  console.log(`Server is listening on port ${PORT}`);
});
  • Route handlers act like user interaction events (click, mouseover, etc.) in the browser

Redirection

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3333;

app.get('/', function(req, res) {
    if(!req.user) res.redirect('/login');
    // other code coes here
});

app.listen(PORT, function() {
  console.log(`Server is listening on port ${PORT}`);
});

Routing

  • Determines what functions run and what data is passed to them
  • Full guide here
  • Route handlers are loaded sequentially, so if 2 routes match the request the one which is written EARLIER will be triggered
  • Route Params:
app.get('/users/:userId/books/:bookId?', function (req, res) { // ? = optional
  res.send(req.params)
});

/*
*   Route path: /users/:userId/books/:bookId
*   Request URL: http://localhost:3000/users/34/books/8989
*   req.params: { "userId": "34", "bookId": "8989" }
*/

Query Params:

// GET /search?q=tobi+ferret
req.query.q
// => "tobi ferret"

Task

  • Create a REST API
  • It can be about anything you want
  • You need 4(5) route handlers
    • GET /{things} - get all records
    • GET /{things}/:id? - get one record
    • POST: /{things} - add new
    • PUT: /{things}/:id - update a thing
    • DELETE /{things}/:id - delete a thing

Answer:

Router

  • Let's say a part of your app deals with cars and another with pets, etc.
  • Your app file would become very crowded with all these routes, PLUS, why send requests to cars if they may accidentally go through pets?!
  • We need a way to group routes/handlers and apply them to URLs only
  • Enter Router (demo)
const petsRouter = express.Router([options]); // local
const carsRouter = require('./routers/cars/');//separate file

const dogs = [{}, {}];
// Triggered by URL /pets/dogs
petsRouter.get('/dogs', function (req, res, next) {
  res.status(200).send(dogs);
});

app.use('/cars', carRouter);
app.use('/pets', petRouter);

Server-side Templating

const express = require('express');
const bodyParser = require('body-parser');
const es6Renderer = require('express-es6-template-engine');
const app = express();
const PORT = process.env.PORT || 3333;

app.use(express.static('public'));

app.engine('html', es6Renderer); // give a renderer a name
app.set('views', 'views'); // needs a 'views' dir with the templates in
app.set('view engine', 'html'); // set the renderer called 'html' as the view rendering program

app.get('/', function(req, res) {
  res.render('index', {
    locals: {
      title: 'Welcome!',
    },
    partials: {
      header: 'header',
      footer: 'footer',
    },
  }, (err, html) => {
    if (err) {
      res.status(500).send(err);
    } else {
      res.send(html);
    }
  });
});

app.listen(PORT, function(){
    console.log('Server is listening');
});

demo repo but modern frameworks (Next.js, Fresh) do this better

Quick Terminology Lesson

  • CSR - Client-side Rendering (what you're used to)
    • bad SEO and accessibility because HTML is hollow page!
  • SSR - Server-side Rendering (First react page is constructed on the server at the point they are requested and sent whole
    • Re-hydration: the JS of the SPA is then run (e.g. populating contexts with models, etc.)
    • <NoSSR> tag: For some things that run exclusively on the client you can exclude with a <NoSSR> tag
  • SSG - Static Site Generation (Gatsby, Next.js option, etc.). All the pages are pre-built when the site is deployed.
    • ISR - Incremental Static Regeneration - Pages are built and cached but if a request is made after cache time old response is sent but page is remade, like SWR (Stale While Revalidate)

File Up/Download

First Servers

By James Sherry

First Servers

First Servers

  • 1,486