Regex, Express, and Full-Stack Development (Week 11) - Learning Objectives

Assessment Structure

Regular Expressions and Node HTTP (W11D1) - Learning Objectives

Regular Expressions

  1. Define the effect of the * operator and use it in a regular expression
  2. Define the effect of the ? operator and use it in a regular expression
  3. Define the effect of the + operator and use it in a regular expression
  4. Define the effect of the . operator and use it in a regular expression
  5. Define the effect of the ^ operator and use it in a regular expression
  6. Define the effect of the $ operator and use it in a regular expression
  7. Define the effect of the [] bracket expression and use it in a regular expression
  8. Define the effect of the - inside brackets and use it in a regular expression
  9. Define the effect of the ^ inside brackets and use it in a regular expression

Regex cheatsheet:

Regex Symbol Meaning
* zero or more
+ one or more
? zero or one
{m, n} from m to n number of characters
^ start of input
$ end of input
. any single character
\ escape a special character
[] match anything inside (or)
[a-z] or [0-9] range of characters
[^a-za-z] not those characters
() group these characters
whitespace
digit
wordy (letter, digit, or _)
not whitespace
not digit
not wordy

Using Regex in JavaScript - aka How Do I Apply This Stuff?

const regex = /pattern/;
const regex = /pattern/gi;
const pattern = /pattern/;

console.log(pattern.test('this pattern is plaid')); // true
console.log(pattern.test('THIS PATTERN IS PLAID')); // false (case-sensitive)
const s = 'An Advancing Aardvark';
const replaced = s.replace(/a/gi, 'X');
console.log(replaced); // Xn XdvXncing XXrdvXrk
let index = 0;
const s = 'An Advancing Aardvark';
const replaced = s.replace(/a/gi, (match) => {
  index += 1;
  return index;
});
console.log(replaced); // 1n 2dv3ncing 45rdv6rk
let test = ['this', 'that', 'the other', 'and this?'];
let indices = test.map(function (e) {
  // search is returning the index that starts the match, or -1 if not found
  return e.search(/(this)|(that)/); // [0, 0, -1, 4]
});
console.log(indices);

HTTP Full-Stack

  1. Identify the five parts of a URL
  foo://example.com:8042/over/there?name=ferret#nose
  \_/   \______________/\_________/ \_________/ \__/
   |           |            |            |        |
scheme     authority       path        query   fragment
  1. Identify at least three protocols handled by the browser
  1. Use an IncomingMessage (typically called the req) object to
const headers = req.headers;
// The headers are represented as a POJO with header names as keys
// { 'user-agent': 'curl/7.22.0',
//   host: '127.0.0.1:8000',
//   accept: '*/*' }
const method = req.method;
// The headers are represented as a string such as 'GET' or 'POST'
  1. Use a ServerResponse (typically called the res) object to
res.statusCode = 400; // We can set the statusCode directly through assignment
res.statusMessage = "That password doesn't match"; // We can specify a custom status message if it differs from the default related to the code
res.setHeader('Content-Type', 'text/plain'); // .setHeader(<<headerName>>, <<headerContent>>)
// If we have a single string to pass as the content we can give it as the argument directly to .end()
res.end('NOT FOUND');

// instead of using .end('message'), we can build up our response with .write('messagePart')
for (let i = 1; i <= 10; i++) {
  res.write(`<p>This is p-tag #${i}</p>`);
}
res.end();

Express and Pug Templates (W11D2) - Learning Objectives

Express

  1. Send plain text responses for any HTTP request
const express = require('express');

// Create the Express app.
const app = express();
const port = 8081;

app.listen(port, () => console.log(`Listening on port ${port}...`));
app.get('/', (req, res) => {
  res.send('Hello from Express!');
});
  1. Use pattern matching to match HTTP request paths to route handlers
  1. Use the Pug template engine to generate HTML from Pug templates to send to the browser
app.set('view engine', 'pug');
// Define a route.
app.all('*', (req, res) => {
  console.log(`Request method: ${req.method}`);
  console.log(`Request path: ${req.path}`);

  res.render('layout');
});
html
  head
    title My Awesome Title
  body
    h1 My Super Cool Heading
  1. Pass data to Pug templates to generate dynamic content
app.all('*', (req, res) => {
  console.log(`Request method: ${req.method}`);
  console.log(`Request path: ${req.path}`);

  res.render('layout', { title: 'Pug Template Syntax Sandbox', heading: 'Welcome to the Sandbox!' });
});
html
  head
    title= title
  body
    h1 My heading says #{heading}
  1. Use the Router class to modularize the definition of routes
// In our main app.js file
const express = require('express');
const userRoutes = require('./routes/users.js'); // see following code block
const tweetRoutes = require('./routes/tweets.js'); // see following code block

const app = express();

app.use('/users', userRoutes);
app.use('/tweets', tweetRoutes);

const port = 4000;

app.listen(port, () => {
  console.log(`Listening on port ${port}...`);
});
// In a users route file
const express = require('express');

const router = express.Router();

router.get('/' (req, res) => {
  res.send('Hello from the base user route. We got here from /users/');
})

router.get('/:id' (req, res) => {
  res.send(`This is the page for user with id ${req.params.id}. We got here from /users/:id`);
})

// other routes, such as editing a profile, deleting a user, etc.

module.exports = router;
// In a tweets route file
const express = require('express');

const router = express.Router();

router.get('/' (req, res) => {
  res.send('Hello from the base tweets route. We got here from /tweets/');
})

router.get('/:id' (req, res) => {
  res.send(`This is the page for tweet with id ${req.params.id}. We got here from /tweets/:id`);
})

// other routes, such as posting a tweet, editing, deleting, etc.

module.exports = router;

Pug Templates

  1. Declare HTML tags and their associated ids, classes, attributes, and content
html
  head
    title My Page
  body
    div#main
      div.blue
      div.yellow
        a(href="http://google.com") Click here
  1. Use conditional statements to determine whether or not to render a block
if isEOD
  h2 Welcome back!
else
  h2 Keep coding!

if (time > 17)
  p See ya tomorrow!
  1. Use interpolation to mix static text and dynamic values in content and attributes
res.render('layout', {
  title: 'Pug demo page',
  header: 'interpolation',
  route: 'tweets',
});
html
  head
    title= title
    style
      include style.css
  body
    h1 Pug does #{header}
    h2 Pug allows you to do many things
    ul
      li: a(href='http://google.com') This is a link to google.com
      li: a(href=`http://mycoolsite.com/${route}`) This is a link to my cool site's #{route} route
  1. Use iteration to generate multiple blocks of HTML based on data provided to the template
// app.js
app.get('/pug', (req, res) => {
  res.render('eod', { colors: ['blue', 'red', 'green'] });
});
ul
  each color in colors
    li= color

HTML Forms (W11D3) - Learning Objectives

HTML Forms

  1. Describe the interaction between the client and server when an HTML form is loaded into the browser, the user submits it, and the server processes it
<form action="/users" method="post">
  <label
    >Username:
    <input type="text" name="user[username]" />
  </label>
  <label
    >Email:
    <input type="email" name="user[email]" />
  </label>
  <label
    >Age:
    <input type="number" name="user[age]" />
  </label>
  <label
    >Password:
    <input type="password" name="user[password]" />
  </label>
  <input type="submit" value="Sign Up" />
</form>
  1. Create an HTML form using the Pug template engine
form(method="post" action="/users")
  input(type="hidden" name="_csrf" value=csrfToken)
  label(for="user[username]") Username:
    input(type="username" id="username" name="username" value=username)
  label(for="user[email]") Email:
    input(type="email" id="email" name="email" value=email)
  label(for="user[age]") Age:
    input(type="age" id="age" name="user[age]" value=age)
  label(for="user[password]") Password:
    input(type="password" id="password" name="user[password]")
  input(type="submit" value="Sign Up")
  1. Use express to handle a form’s POST request
app.get('/create', csrfProtection, (req, res, next) => {
  res.render('create', {
    title: 'Create a user',
    errors: [],
    csrfToken: req.csrfToken(),
  });
});

app.post(`/users`, csrfProtection, checkFields, async (req, res) => {
  const { username, email, age, password } = req.body.user;

  // Our errors attribute was created by our checkFields middleware
  // If we had errors, we're rendering the 'create' form again, passing along the errors as well as the user data so that we can prepopulate those fields with the values that were originally submitted
  if (req.errors.length >= 1) {
    res.render(`create`, {
      title: 'Create a user',
      errors: req.errors,
      username,
      email,
      age,
      csrfToken: req.csrfToken(),
    });
    return;
  }

  await User.create({ username, email, age, password });

  res.redirect(`/`);
});
  1. Use the built-in express.urlencoded() middleware function to parse incoming request body form data
app.use(
  express.urlencoded({
    extended: true,
  })
);
  1. Explain what data validation is and why it’s necessary for the server to validate incoming data
  1. Validate user-provided data from within an Express route handler function
app.post('/create', csrfProtection, async (req, res) => {
  const { username, email, age, password, confirmedPassword } = req.body;
  const errors = [];

  if (!username) {
    errors.push('Please provide a username.');
  }

  if (!email) {
    errors.push('Please provide an email.');
  }

  if (!age) {
    errors.push('Please provide an age.');
  }

  const ageAsNum = Number.parseInt(age, 10);

  if (age && (ageAsNum < 0 || ageAsNum > 120)) {
    errors.push('Please provide an age between 0 and 120');
  }

  if (!password) {
    errors.push('Please provide a password.');
  }

  if (password && password !== confirmedPassword) {
    errors.push('The provided values for the password and password confirmation fields did not match.');
  }

  if (errors.length > 0) {
    res.render('create', {
      title: 'Create a user',
      username,
      email,
      age,
      csrfToken: req.csrfToken(),
      errors,
    });
    return;
  }

  const newUser = await User.create({ username, email, age, password });
  res.redirect(`/user/${newUser.id}`);
});
  1. Write a custom middleware function that validates user-provided data
const validationMiddleware = (req, res, next) => {
  const { username, email, age, password, confirmedPassword } = req.body;
  const ageAsNum = Number.parseInt(age, 10);
  const errors = [];

  if (!username) {
    errors.push('Please provide a username.');
  }

  if (!email) {
    errors.push('Please provide an email.');
  }

  if (!age) {
    errors.push('Please provide an age.');
  }

  if (age && (ageAsNum < 0 || ageAsNum > 120)) {
    errors.push('Please provide an age between 0 and 120');
  }

  if (!password) {
    errors.push('Please provide a password.');
  }

  if (password && password !== confirmedPassword) {
    errors.push('The provided values for the password and password confirmation fields did not match.');
  }

  req.errors = errors;
  next();
};
app.post('/create', csrfProtection, validationMiddleware, async (req, res) => {
  const { firstName, lastName, email, password, confirmedPassword } = req.body;
  const errors = req.errors;

  if (errors.length > 0) {
    res.render('create', {
      title: 'Create a user',
      username,
      email,
      age,
      csrfToken: req.csrfToken(),
      errors,
    });
    return;
  }

  await User.create({ username, email, age, password });
  res.redirect('/');
});
  1. Use the csurf middleware to embed a token value in forms to protect against Cross-Site Request Forgery exploits
app.use(cookieParser());
const csrfProtection = csrf({ cookie: true });
const express = require('express');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');

const { User } = require('./models');

const app = express();

const port = process.env.PORT || 3000;

const csrfProtection = csrf({ cookie: true });

app.use(cookieParser());
app.use(express.urlencoded({ extended: true }));
app.set('view engine', 'pug');

app.get('/create', csrfProtection, (req, res, next) => {
  res.render('create', {
    title: 'Create a user',
    messages: [],
    csrfToken: req.csrfToken(),
  });
});

app.post('/create', csrfProtection, validationMiddleware, async (req, res) => {
  const { username, email, password } = req.body;
  const errors = req.errors;

  if (errors.length > 0) {
    res.render('create', {
      title: 'Create a user',
      username,
      email,
      age,
      csrfToken: req.csrfToken(),
      errors,
    });
    return;
  }

  await User.create({ username, email, age, password });
  res.redirect('/');
});
// Other content before the form
form(action='/create' method='post')
  input(type='hidden' name="_csrf" value=csrfToken)
  div(class="form-group")
    label(for='username') Username:
    input(id='username' class="form-control" name='username' value=username type='text')
  // Other form fields
  div
    input(type='submit' value="Create User" class='btn btn-primary')

Full-Stack (Data-Driven Web Sites) (W11D4) - Learning Objectives

Data-Driven Web Sites

  1. Use environment variables to specify configuration of or provide sensitive information for your code
  1. Use the dotenv npm package to load environment variables defined in an .env file
  1. Recall that Express cannot process unhandled Promise rejections from within route handler (or middleware) functions.
  1. Use a Promise catch block or a try/catch statement with async/await to properly handle errors thrown from within an asynchronous route handler (or middleware) function
app.get('*', async (req, res, next) => {
  // we specify an async function and capture the 'next' parameter
  try {
    const result = await someAsynchronousFunction();
    res.send(result);
  } catch (err) {
    // If an error occurs in the above try block, it is captured as err
    next(err); // The err variable that we captured is passed to next so that error handlers can interact with it
  }
});
app.get('*', async (req, res, next) => {
  someAsyncFunction()
    .then(() => {
      // Assume some command is here that throws an error
    })
    .catch((err) => {
      // We catch it here to make express happy
      next(err);
    });
});
  1. Write a wrapper function to simplify catching errors thrown within asynchronous route handler (or middleware) functions
  1. Use the morgan npm package to log requests to the terminal window to assist with auditing and debugging
  1. Add support for the Bootstrap front-end component library to a Pug layout template
  doctype html
  html
    head
      meta(charset='utf-8')
      meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')

      // The following line is importing the bootstrap css file
      link(rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css' integrity='sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh' crossorigin='anonymous')

      title Reading List - #{title}
    body
      nav(class='navbar navbar-expand-lg navbar-dark bg-primary')
        a(class='navbar-brand' href='/') Reading List
      .container
        h2(class='py-4') #{title}
        block content

      // The following lines are importing the bootstrap js files
      script(src='https://code.jquery.com/jquery-3.4.1.slim.min.js' integrity='sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n' crossorigin='anonymous')
      script(src='https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js' integrity='sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo' crossorigin='anonymous')
      script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js' integrity='sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6' crossorigin='anonymous')
  1. Install and configure Sequelize within an Express application.
  1. Use Sequelize to test the connection to a database before starting the HTTP server on application startup
  1. Define a collection of routes (and views) that perform CRUD operations against a single resource using Sequelize
  1. Handle Sequelize validation errors when users are attempting to create or update data and display error messages to the user so that they can resolve any data quality issues
  1. Describe how an Express.js error handler function differs from middleware and route handler functions
  1. Define a global Express.js error-handling function to catch and process unhandled errors
// (middleware and routes defined above)

// Generic error handler.
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  const isProduction = process.env.NODE_ENV === 'production';
  res.render('error', {
    title: 'Server Error',
    message: isProduction ? 'An error occurred!' : err.message,
    stack: isProduction ? null : err.stack,
  });
});
  1. Define a middleware function to handle requests for unknown routes by returning a 404 NOT FOUND error