Express Flashcards

1
Q

LEARN EXPRESS ROUTES

Introduction

A

A huge portion of the Internet’s data travels over HTTP/HTTPS through request-response cycles between clients and servers. Every time that you use a website, your browser sends requests to one or many servers requesting resources. Every image, meme, post, and video is requested by a client and sent in a response from a server.

Express is a powerful but flexible Javascript framework for creating web servers and APIs. It can be used for everything from simple static file servers to JSON APIs to full production servers.

In this lesson, you will be fixing a machine called Express Yourself in the browser. The machine is supposed to provide functionality for clients to interact with various Expressions: JavaScript objects each containing ids, names, and emojis. Currently, it looks nice, but nothing works since there is no server in place! You will be learning all the necessary skills to implement an API allowing clients to Create, Read, Update, and Delete Expressions. These four functionalities together are known as CRUD, and they form the backbone of many real-life APIs.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

LEARN EXPRESS ROUTES

Starting A Server

A

Express is a Node module, so in order to use it, we will need to import it into our program file. To create a server, the imported express function must be invoked.

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

On the first line, we import the Express library with require. When invoked on the second line, it returns an instance of an Express application. This application can then be used to start a server and specify server behavior.

The purpose of a server is to listen for requests, perform whatever action is required to satisfy the request, and then return a response. In order for our server to start responding, we have to tell the server where to listen for new requests by providing a port number argument to a method called app.listen(). The server will then listen on the specified port and respond to any requests that come into it.

The second argument is a callback function that will be called once the server is running and ready to receive responses.

const PORT = 4001; //const PORT = process.env.PORT || 4001;
app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

In this example, our app.listen() call will start a server listening on port 4001, and once the server is started it will log ‘Server is listening on port 4001’.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

LEARN EXPRESS ROUTES

Writing Your First Route

A

Once the Express server is listening, it can respond to any and all requests. But how does it know what to do with these requests? To tell our server how to deal with any given request, we register a series of routes. Routes define the control flow for requests based on the request’s path and HTTP verb.

For example, if your server receives a GET request at /monsters, we will use a route to define the appropriate functionality for that HTTP verb (GET) and path (/monsters).

The path is the part of a request URL after the hostname and port number, so in a request to localhost:4001/monsters, the path is /monsters (in this example, the hostname is localhost, the port number is 4001).

The HTTP verb is always included in the request, and it is one of a finite number of options used to specify expected functionality. GET requests are used for retrieving resources from a server, and we will discuss additional request types in later exercises.

Express uses app.get() to register routes to match GET requests. Express routes (including app.get()) usually take two arguments, a path (usually a string), and a callback function to handle the request and send a response.

const moods = [{ mood: 'excited about express!'}, { mood: 'route-tastic!' }];
app.get('/moods', (req, res, next) => {
  // Here we would send back the moods array in response
});

The route above will match any GET request to ‘/moods’ and call the callback function, passing in two objects as the first two arguments. These objects represent the request sent to the server and the response that the Express server should eventually send to the client.

If no routes are matched on a client request, the Express server will handle sending a 404 Not Found response to the client.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

LEARN EXPRESS ROUTES

Sending A Response

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

LEARN EXPRESS ROUTES

Matching Route Paths

A

Express tries to match requests by route, meaning that if we send a request to <server>:<port>/api-endpoint, the Express server will search through any registered routes in order and try to match /api-endpoint.</port></server>

Express searches through routes in the order that they are registered in your code. The first one that is matched will be used, and its callback will be called.

In the example to the right, you can see two .get() routes registered at /another-route and /expressions. When a GET /expressions request arrives to the Express server, it first checks /another-route‘s path because it is registered before the /expressions route. Because /another-route does not match the path, Express moves on to the next registered middleware. Since the route matches the path, the callback is invoked, and it sends a response.

If there are no matching routes registered, or the Express server has not sent a response at the end of all matched routes, it will automatically send back a 404 Not Found response, meaning that no routes were matched or no response was ultimately sent by the registered routes.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

LEARN EXPRESS ROUTES

Getting A Single Expression

A

Routes become much more powerful when they can be used dynamically. Express servers provide this functionality with named route parameters. Parameters are route path segments that begin with : in their Express route definitions. They act as wildcards, matching any text at that path segment. For example /monsters/:id will match both/monsters/1 and /monsters/45.

Express parses any parameters, extracts their actual values, and attaches them as an object to the request object: req.params. This object’s keys are any parameter names in the route, and each key’s value is the actual value of that field per request.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

LEARN EXPRESS ROUTES

Setting Status Codes

A

Express allows us to set the status code on responses before they are sent. Response codes provide information to clients about how their requests were handled. Until now, we have been allowing the Express server to set status codes for us. For example, any res.send() has by default sent a 200 OK status code.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

LEARN EXPRESS ROUTES

Matching Longer Paths

A

Parameters are extremely helpful in making server routes dynamic and able to respond to different inputs. Route parameters will match anything in their specific part of the path, so a route matching /monsters/:name would match all the following request paths:
~~~
/monsters/hydra
/monsters/jörmungandr
/monsters/manticore
/monsters/123
~~~
In order for a request to match a route path, it must match the entire path, as shown in the diagram to the right. The request arrives for /expressions/1. It first tries to match the /expressions route, but because it has additional path segments after /expressions, it does not match this route and moves on to the next. It matches /expressions/:id because :id will match any value at that level of the path segment. The route matches, so the Express server calls the callback function, which in turn handles the request and sends a response.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

LEARN EXPRESS ROUTES

Other HTTP Methods

A

HTTP Protocol defines a number of different method verbs with many use cases. So far, we have been using the GET request which is probably the most common of all. Every time your browser loads an image, it is making a GET request for that file!

This course will cover three other important HTTP methods: PUT, POST, and DELETE. Express provides methods for each one: app.put(), app.post(), and app.delete().

PUT requests are used for updating existing resources. In our Express Yourself machine, a PUT request will be used to update the name or emoji of an expression already saved in our database. For this reason, we will need to include a unique identifier as a route parameter to determine which specific resource to update.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

LEARN EXPRESS ROUTES

Using Queries

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

LEARN EXPRESS ROUTES

Matching By HTTP Verb

A

Express matches routes using both path and HTTP method verb. In the diagram to the right, we see a request with a PUT verb and /expressions (remember that the query is not part of the route path). The path for the first route matches, but the method verb is wrong, so the Express server will continue to the next registered route. This route matches both method and path, and so its callback is called, the necessary updating logic is executed, and the response is sent.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

LEARN EXPRESS ROUTES

Creating An Expression

A

POST is the HTTP method verb used for creating new resources. Because POST routes create new data, their paths do not end with a route parameter, but instead end with the type of resource to be created.

For example, to create a new monster, a client would make a POST request to /monsters. The client does not know the id of the monster until it is created and sent back by the server, therefore POST /monsters/:id doesn’t make sense because a client couldn’t know the unique id of a monster before it exists.

Express uses .post() as its method for POST requests. POST requests can use many ways of sending data to create new resources, including query strings.

The HTTP status code for a newly-created resource is 201 Created.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

LEARN EXPRESS ROUTES

Deleting Old Expressions

A

DELETE is the HTTP method verb used to delete resources. Because DELETE routes delete currently existing data, their paths should usually end with a route parameter to indicate which resource to delete.

Express uses .delete() as its method for DELETE requests.

Servers often send a 204 No Content status code if deletion occurs without error.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

LEARN EXPRESS ROUTES

Adding Animals Routes
1.In your app.js file, Create a GET /animals route to return an array of all animals.
2.Create a GET /animals/:id route to respond with a single animal.
3.Create a PUT /animals/:id route to update an animal in animals and send back the updated animal.
4.Create a POST /animals route to add new animals to the animals and respond with the new animal.
5.Create a DELETE /animals/:id route to delete animals by ID.

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

LEARN EXPRESS ROUTES

get

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

const PORT = process.env.PORT || 4001;

const battlefields = {
  fortSumter: {
    state: 'SC',
  },
  manassas: {
    state: 'VA',
  },
  gettysburg: {
    state: 'PA',
  },
  antietam: {
    state: 'MD',
  }
}

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

app.get('/battlefields/:name', (req, res, next) => {
  const battlefieldName = req.params.name;
  const battlefield = battlefields[battlefieldName]
  if (battlefield) {
    res.send(battlefield);
  } else {
    res.status(404).send();
  } 
});
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

LEARN EXPRESS ROUTES

put

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

const PORT = process.env.PORT || 4001;

const currencies = {
  diram: {
    countries: ['UAE', 'Morocco'],
  },
  real: {
    countries: ['Brazil'],
  },
  dinar: {
    countries: ['Algeria', 'Bahrain', 'Jordan', 'Kuwait'],
  },
  vatu: {
    countries: ['Vanuatu'],
  },
  shilling: {
    countries: ['Tanzania', 'Uganda', 'Somalia', 'Kenya'],
  },
};

app.put('/currencies/:name/countries', (req,res,next) => {
  const currencyName = req.params.name;
  currencies[currencyName] = req.query;
  res.send(currencies[currencyName]);
});

app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});
16
Q

LEARN EXPRESS ROUTES

post

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

const PORT = process.env.PORT || 4001;

const soups = ['gazpacho', 'borscht', 'primordial', 'avgolemono', 'laksa'];

app.post('/soup', (req, res, next) => {
  const newSoup = req.query.name;
  soups.push(newSoup);
  res.status(201).send(newSoup);
})

app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});
17
Q

LEARN EXPRESS ROUTES

delete

A

hit: findPuddingIndex returns -1 if a pudding name is not included inpuddingFlavors.
~~~
const express = require(‘express’);
const app = express();

const PORT = process.env.PORT || 4001;

const puddingFlavors = [‘chocolate’, ‘banana’, ‘butterscotch’, ‘pistachio’];

const findPuddingIndex = (name) => {
return puddingFlavors.indexOf(name);
}

const deletePuddingAtIndex = (index) => {
puddingFlavors.splice(index, 1);
}

// Your code here!
app.delete(‘/puddings/:flavor’, (req, res, next) => {
const flavorIndex = findPuddingIndex(req.params.flavor);
if (flavorIndex !== -1) {
deletePuddingAtIndex(flavorIndex);
res.status(204).send();
} else {
res.status(404).send();
}
})

app.listen(PORT, () => {
console.log(Server is listening on port ${PORT});
});
~~~

18
Q

LEARN EXPRESS ROUTES

delete

A

hit: findPuddingIndex returns -1 if a pudding name is not included inpuddingFlavors.
~~~
const express = require(‘express’);
const app = express();

const PORT = process.env.PORT || 4001;

const puddingFlavors = [‘chocolate’, ‘banana’, ‘butterscotch’, ‘pistachio’];

const findPuddingIndex = (name) => {
return puddingFlavors.indexOf(name);
}

const deletePuddingAtIndex = (index) => {
puddingFlavors.splice(index, 1);
}

// Your code here!
app.delete(‘/puddings/:flavor’, (req, res, next) => {
const flavorIndex = findPuddingIndex(req.params.flavor);
if (flavorIndex !== -1) {
deletePuddingAtIndex(flavorIndex);
res.status(204).send();
} else {
res.status(404).send();
}
})

app.listen(PORT, () => {
console.log(Server is listening on port ${PORT});
});
~~~

19
Q

LEARN EXPRESS ROUTES

router

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

const PORT = process.env.PORT || 4001;

const mountains = ['denali', 'olympus', 'kilimanjaro', 'matterhorn'];
const mountainRanges = ['alps', 'andes', 'himalayas', 'rockies'];

const mountainsRouter = express.Router();
const mountainRangesRouter = express.Router();

app.use('/mountains', mountainsRouter);
app.use('/mountain-ranges', mountainRangesRouter);

mountainsRouter.get('/', (req, res, next) => {
  res.send(mountains)
})

mountainRangesRouter.get('/', (req, res, next) => {
  res.send(mountainRanges)
})

app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});
20
A
21
A

By now you may have noticed that our efforts to not repeat ourselves have resulted in us putting the same function call over and over throughout our code. Isn’t that somewhat contradictory? You would be absolutely right to think so.

So how do we get code to run every time one of our Express routes is called without repeating ourselves? We write something called middleware. Middleware is code that executes between a server receiving a request and sending a response. It operates on the boundary, so to speak, between those two HTTP actions.

In Express, middleware is a function. Middleware can perform logic on the request and response objects, such as: inspecting a request, performing some logic based on the request, attaching information to the response, attaching a status to the response, sending the response back to the user, or simply passing the request and response to another middleware. Middleware can do any combination of those things or anything else a Javascript function can do.
~~~
app.use((req, res, next) => {
console.log(‘Request received’);
});
~~~
The previous code snippet is an example of middleware in action. app.use() takes a callback function that it will call for every received request. In this example, every time the server receives a request, it will find the first registered middleware function and call it. In this case, the server will find the callback function specified above, call it, and print out ‘Request received’.

You might be wondering what else our application is responsible for that isn’t related to middleware. The answer is not much. To quote the Express documentation:

An Express application is essentially a series of middleware function calls.

It is precisely this service that we leverage Express for. In addition to performing the routing that allows us to communicate appropriate data for each separate endpoint, we can perform application logic we need by implementing the necessary middleware.

22
Q

MIDDLEWARE

next()

A

It seems like our middleware was successful — it logged out the type of each request received. But then the response stopped there! What happened? We mentioned that most of Express’s functionality is chaining middleware. This chain of middleware is referred to as the middleware stack.

The middleware stack is processed in the order they appear in the application file, such that middleware defined later happens after middleware defined before. It’s important to note that this is regardless of method — an app.use() that occurs after an app.get() will get called after the app.get(). Observe the following code:
In the above code, the routes are called in the order that they appear in the file, provided the previous route called next() and thus passed control to the next middleware. We can see that the final matching call was not printed. This is because the previous middleware did not invoke the next() function to run the following middleware.

An Express middleware is a function with three parameters: (req, res, next). The sequence is expressed by a set of callback functions invoked progressively after each middleware performs its purpose. The third argument to a middleware function, next, should get explicitly called as the last part of the middleware’s body. This will hand off the processing of the request and the construction of the response to the next middleware in the stack.

24
A

We learned that app.use() takes a path parameter, but we never fully investigated what that path parameter could be. Let’s take another look at the Express documentation for app.use():

“argument: path

description: The path for which the middleware function is invoked; can be any of:

A string representing a path.
A path pattern.
A regular expression pattern to match paths.
An array of combinations of any of the above. “
So app.use() can take an array of paths! That seems like a handy way to rewrite the code from our last exercise so that we don’t have to put the same code in two different routes with different paths.

25
Q

MIDDLEWARE

Middleware Stacks

A
25
A

We will replace the logging code in the workspace withmorgan, an open-source library for logging information about the HTTP request-response cycle in a server application.morgan()is a function that willreturn a middleware function, to reiterate: the return value ofmorgan()will be a function, that function will have the function signature(req, res, next)that can be inserted into anapp.use(), and that function will be called before all following middleware functions. Morgan takes an argument to describe the formatting of the logging output. For example,morgan(‘tiny’)will return a middleware function that does a “tiny” amount of logging. With morgan in place, we’ll be able to remove the existing logging code. Once we see how fast it is to add logging with morgan, we won’t have to spend time in the future trying to figure out how to replicate that functionality.

26
A

Being able to use open-source middleware can certainly make our jobs as programmers a lot easier. Not only does it prevent us from having to write the same code every time we want to accomplish a common task, it allows us to perform some tasks that would take a lot of research for us to implement.

When we implement middleware, we take in thereqobject, so that we can see information about the request. This object includes a good deal of important information about the request that we can use to inform our response, however for some requests it misses a fundamental piece. An HTTP request can include abody, a set of information to be transmitted to the server for processing. This is useful when the end user needs to send information to the server. If you’ve ever uploaded a post onto a social media website or filled out a registration form chances are you’ve sent an HTTP request with a body. The lucky thing about using open-source middleware is that even though parsing the body of an HTTP request is a tricky operation requiring knowledge about network data transfer concepts, we easily manage it by importing a library to do it for us.

If we look at ourbodyParser, we see a simplified version of how one might perform request body parsing. Let’s see if there’s a better way that doesn’t involve us trying to create our own body-parser. Maybe we can find a library that does it for us?

Take a look atbody-parser. “Node.js body parsing middleware”, that’s just what we needed! Let’s see if we can use thisdependencyinstead of trying to manage our own body-parsing library.

27
A
28
A

While it’s good to know how to write error-handling middleware, it’s a natural curiosity that causes us to ask “isn’t error-handling a common task? Has someone written middleware that performs it for us?” Let’s take a look at the list of Express middleware. This list of middleware includes many things the creators of Express maintain, some of which was included in Express in previous versions. The movement on the Express team’s part to identify separate functionality and modularize their code into independent factors allows developers like us to only take what we need. In this way, they can make major updates to each middleware individually and programmers who do not use that middleware won’t have to worry about their version of Express being out of date.

Can you find something on that list that will help us handle errors?

28
A

While it’s good to know how to write error-handling middleware, it’s a natural curiosity that causes us to ask “isn’t error-handling a common task? Has someone written middleware that performs it for us?” Let’s take a look at the list of Express middleware. This list of middleware includes many things the creators of Express maintain, some of which was included in Express in previous versions. The movement on the Express team’s part to identify separate functionality and modularize their code into independent factors allows developers like us to only take what we need. In this way, they can make major updates to each middleware individually and programmers who do not use that middleware won’t have to worry about their version of Express being out of date.

Can you find something on that list that will help us handle errors?

28
A

While it’s good to know how to write error-handling middleware, it’s a natural curiosity that causes us to ask “isn’t error-handling a common task? Has someone written middleware that performs it for us?” Let’s take a look at the list of Express middleware. This list of middleware includes many things the creators of Express maintain, some of which was included in Express in previous versions. The movement on the Express team’s part to identify separate functionality and modularize their code into independent factors allows developers like us to only take what we need. In this way, they can make major updates to each middleware individually and programmers who do not use that middleware won’t have to worry about their version of Express being out of date.

Can you find something on that list that will help us handle errors?

28
A

While it’s good to know how to write error-handling middleware, it’s a natural curiosity that causes us to ask “isn’t error-handling a common task? Has someone written middleware that performs it for us?” Let’s take a look at the list of Express middleware. This list of middleware includes many things the creators of Express maintain, some of which was included in Express in previous versions. The movement on the Express team’s part to identify separate functionality and modularize their code into independent factors allows developers like us to only take what we need. In this way, they can make major updates to each middleware individually and programmers who do not use that middleware won’t have to worry about their version of Express being out of date.

Can you find something on that list that will help us handle errors?

29
Q

ROUTER PARAMETERS

Introduction

A
30
Q

ROUTER PARAMETERS

router.param()

A

In the code above we intercept any request to a route handler with the :spellId parameter. Note that in the app.param function signature, ‘spellId’ does not have the leading :. The actual ID will be passed in as the fourth argument, id in this case, to the app.param callback function when a request arrives.

We look up the spell in our SpellBook array using the .find() method. If SpellBook does not exist, or some other error is thrown in this process, we pass the error to the following middleware (hopefully we’ve written some error-handling middleware, or included some externally-sourced error-handling middleware). If the spell exists in SpellBook, the .find() method will store the spell in found, and we attach it as a property of the request object (so future routes can reference it via req.spell). If the requested spell does not exist, .find() will store undefined in found, and we let future middlewares know there was an error with the request by creating a new Error object and passing it to next().

Note that inside an app.param callback, you should use the fourth argument as the parameter’s value, not a key from the req.params object.

31
Q

ROUTER PARAMETERS

Merge Parameters

A

Complexity is all around us. Buildings are made from bricks and many droplets of water make a cloud. When we want to create something complex in software, we model out our base components and use composition to create these relationships.

When we’re building web endpoints, we might want to access some property of a complex object. In order to do this in Express, we can design a nested router. This would be a router that is invoked within another router. In order to pass parameters the parent router has access to, we pass a special configuration object when defining the router.

In the code above we define two endpoints: /sorcerer and /sorcerer/:sorcererId/familiars. The familiars are nested into the sorcerer endpoint — indicating the relationship that a sorcerer has multiple familiars. Take careful note of the {mergeParameters: true} argument that gets passed when creating the familiarRouter. This argument tells Express that the familiarRouter should have access to parents passed into its parent router, that is, the sorcererRouter. We then tell express that the path for the familiarRouter is the same as the path for the sorcererRouter with the additional path /:sorcererId/familiars. We then can create a family of routes (a router) built by appending routes to familiarRouter‘s base: /sorcerer/:sorcererId/familiars.