参考资料 from Codecademy

List of HTTP status codes

We dicuess about how the front-end consists of the information sent to a client so that a user can see and interact with a website, but where does the information come from? The answer is web server.

A web server is a process running on a computer that listens for incoming requests for information over the Internet and sends back responses. Each time a user navigates to a website on their browser, the broswer makes a request to the web server of that website. Every website has at least one web server. A large company like Facebook has thousands of powerful computers running web servers in facilities located all around the world which are llistening for requests, but we could also run a simple web server from our own computer.

The specific format of a request (and the resulting response) is called the protocol. You might be familiar with the protocol used to access websites: HTTP. When a visitor navigates to a website on their browser, similarly to how one places an order for takeout, they make an HTTP request for the resources that make up that site.

For the simplest websites, a cllient makes a single request. The web server receives that request and sends the cllient a response containing everything needed to view the website. This does not mean the website is not interactive. As with the individual static assets, a website is static because once those files are received, they do not change or move. A static website might be a good choice for a simple personal website with a short boi and family photos. A user navigating Twitter, however, wants acess to new content as it’s created, which a static webiste could not provide.

A static website is like ordering takeout, but modern web applications are like dining in person at a sit-down restaurant. A restaurant patron might order drinks, different courses, make substitutions, or ask questios of waiter. To accomplish this level of complexity, an equally complex back-end is required.

Storing Data

There are many different databases, but we can divide them into 2 types:
relational databases and non-relational databases (also known as NoSQL databases).
Relational databases store information in tables with columns and rows.(MySQL, PostgreSQL)
Non-relational databases might use other systems such as key-value pairs or a document storage model. (MongoDB, Redis)

In addition to the database itself, the back-end needs a way to programmatically access, change, and analyze the data stored there.

The Node REPL

REPL is an abbreviation for read-eval-print loop. It is a program that loops, or repeatedly cycles, through three different states: a read state where the prgram reads input from a user, the eval state where the program evalluates the user’s input, and the print state where the program prints out its evaluation to a console. Then it loops through these states again.

By default, you indicate the input is ready for eval when you hit enter. If you’d like to type multiple lines and then have them evaluated at once you can type .editor while in the REPL. Once in “editor” mode, you can type CONTROL D when you’re ready for the input to be evaluated. Each session of the REPL has a single shared memory; you can access any variables or functions you define until you exit the REPL.

The Node environment contains a number of Node-specific global elements in addition to those built into the javascript language. Every Node-specific global property sits inside the the Node global object. This object contains a number of useful properties and methods that are available anywhere in the Node environment.

Node Package Manager

NPM, stands for Node Package Manager, is an online collection, or registry, of software. Developers can share code they are written to the registry or downloaded code provided by other developers.
One package we like is nodemon. It’s a powerful tool for development in Node that watches all the files in a project you’re working on, and automatically restarts your application when any of them change.

Event-Driven Architecture

Node is often described as having event-driven architecture. In the traditional imperative programming, we give the computer a series of instructions to execute in predefined order. In contrast, when we write web applications, we often need to write logic to handle situations without knowing exactly when they will occur. For example, when programming a website, we might provide functionality for a click event without knowing when a user will trigger it. When Node was created, it applied this same concept of event-driven principles to the back-end environment.

Node provides an EventEmitter class which we can access by requiring in the events core module:

// Require in the 'events' core module
let events = require('events');// Create an instance of the EventEmitter class
let myEmitter = new events.EventEmitter();

Each event emitter instance has an .on method which assigns a listener callback function to a named event. The .on() method takes as its first argument the name of the event as a string and, as its second argument, the listener callback function.

Each event emitter instance also has an .emit() method which announces a named event has occurred. The .emit() method takes as its first argument the name of the event as a string and, as its second argument, the data that should be passed into the listener callback function.

Asynchronous JavaScript with Node.js

In server-side development, we often perform time-consuming tasks such as reading files or querying a database. Instead of halting the execution of our code to await these operations or using multiple threads like other back end environments, Node was designed to use an event loop like the one used in browser-based JavaScript execution. The event-loop enables asynchronous actions to be handled in a non-blocking way.

User Input/ Output

Output is any data or feedback that a computer provides (like to a human user), while input is provided to the computer. In the Node environment, the console is the terminal, and the console.log() method is a “thin wrapper” on the .stdout.write() method of the process object. stdout stands for standard output.

In Node, we can also receive input from a user through the terminal using the stdin.on() method on the process object:

process.stdin.on('data', (userInput) => {let input = userInput.toString()console.log(input)
});

Here, we were able to use .on() because under the hood process.stdin is an instance of EventEmitter. When a user enters text into the terminal and hits enter, a ‘data’ event will be fired and our anonymous listener callback will be invoked. The userInput we receive is an instance of the Node Buffer class, so we convert it to a string before printing.

Errors

The Node environment has all the standard JS errors such as EvalError, SyntaxError, RangeError, ReferenceError, TypeError, and URIError…

Within our own code, we can generate errors and throw them, and, with synchronous code in Node, we can use error handling techniques such as try… catch statements.

There is important difference - “error-first” approach is for asynchronous calls, try-catch is for synchronous.

Filesystem

const fs = require('fs');let secretWord = null;let readDataCallback = (err, data) => {if (err) {console.log(`Something went wrong: ${err}`);} else {console.log(`Provided file contained: ${data}`);}
};//fs.readFile('./fileOne.txt', 'utf-8', readDataCallback);
//fs.readFile('./anotherFile.txt', 'utf-8', readDataCallback);
fs.readFile('./finalFile.txt', 'utf-8', readDataCallback);

Readable Streams

In more realistic scenarios, data is not processed all at once but rather sequentially, piece by piece, in what is known as a stream. Streaming data is often preferable since you do not need enough RAM to process all the data at once nor do you need to have all the data on hand to begin processing it.

One of the simplest uses of streams is reading and writing to files line-by-line. To read files line-by-line, we can use the .createInterface() method from the readline core module. .createInterface() returns an EventEmitter set up to emit ‘line’ events:

const readline = require('readline');
const fs = require('fs');const myInterface = readline.createInterface({input: fs.createReadStream('text.txt')
});myInterface.on('line', (fileLine) => {console.log(`The line read: ${fileLine}`);
});

Let’s walk through the above code:

We require in the readline and fs core modules.

  • We assign to myInterface the returned value from invoking readline.createInterface() with an object containing our designated input.
  • We set our input to fs.createReadStream(‘text.txt’) which will create a stream from the text.txt file.
  • Next we assign a listener callback to execute when line events are emitted. A ‘line’ event will be emitted after each line from the file is read.
  • Our listener callback will log to the console ‘The line read: [fileLine]’, where [fileLine] is the line just read.

Writable Streams

In the previous exercise, we were reading data from a stream, but we can also write to streams! We can create a writeable stream to a file using the fs.createWriteStream() method:

const fs = require('fs')const fileStream = fs.createWriteStream('output.txt');fileStream.write('This is the first line!');
fileStream.write('This is the second line!');
fileStream.end();

In the code above, we set the output file as output.txt. Then we .write() lines to the file. Unlike a readable stream, which ends when it has no more data to read, a writable stream could remain open indefinitely. We can indicate the end of a writable stream with the .end() method.

Let’s combine our knowledge of readable and writable streams to create a program which reads from one text file and then writes to another.

Create an HTTP Server

Node was designed with back end development needs as a top priority. One of these needs is the ability to create web servers, computer processes that listen for requests from clients and return responses. A Node core module designed to meet these needs is the http module. This module contains functions which simplify interacting with HTTP and streamline receiving and responding to requests.

The http.createServer() method returns an instance of an http.server. An http.server has a method .listen() which causes the server to “listen” for incoming connections. When we run http.createServer() we pass in a custom callback function (often referred to as the requestListener). This callback function will be triggered once the server is listening and receives a request.

Let’s break down how the requestListener callback function works:

The function expects two arguments: a request object and a response object.
Each time a request to the server is made, Node will invoke the provided requestListener callback function, passing in the request and response objects of the incoming request.
Request and response objects come with a number of properties and methods of their own, and within the requestListener function, we can access information about the request via the request object passed in.
The requestListener is responsible for setting the response header and body.
The requestListener must signal that the interaction is complete by calling the response.end() method.

const http = require('http');let requestListener = (request, response) => {response.writeHead(200, {'Content-Type': 'text/plain' });response.write('Hello World!\n');response.end();
};const server = http.createServer(requestListener);server.listen(3000);

Let’s walk through the above code:

We required in the http core module.
We created a server variable assigned to the return value of the http.createServer() method.
We invoked http.createServer() with our requestListener callback. This is similar to running the .on() of an EventEmitter: the requestListener will execute whenever an HTTP request is sent to the server on the correct port.
Within the requestListener callback, we make changes to the response object, response, so that it can send the appropriate information to the client sending the request. The status code 200 means that no errors were encountered. The header communicates that the file type is text, rather than something like audio or compressed data.
The last line starts the server with the port 3000. Every server on a given machine specifies a unique port so that traffic can be correctly routed.
You could run the above code on your local machine, and access it by visiting http://localhost:3000/ from your browser. “localhost” is used to refer to the same computer that’s running the current Node process.

In our example web server, we showed you a handful of the methods available on response objects. Be sure to check out the documentation to learn other methods and properties available on response and request objects.

Review

Awesome work! You’ve learned a lot about Node.js including:

  • Node.js is a JavaScript runtime, an environment that allows us to execute our JavaScript code by converting it into something a computer can understand.
  • REPLs are processes that read, evaluate, print, and repeat (loop), and Node.js comes with its own REPL we can access in our terminal with the node command.
    We run JavaScript programs with Node in the terminal by typing node followed by the file name (if we’re in the same directory) or the absolute path of the file.
  • Code can be organized into separate files, modules, and combined through requiring them where needed using the require() function.
  • In addition to core modules, modules included within the environment to efficiently perform common tasks, we can also create our own modules using module.exports and the require() function.
  • We can access NPM, a registry of hundreds of thousands of packages of re-usable code from other developers, directly through our terminal.
  • Node has an event-driven architecture.
    We can make our own instances of the EventEmitter class and we can subscribe to listen for named events with the .on() method and emit events with the .emit() method.
    Node uses an event loop which enables asynchronous actions to be handled in a non-blocking way by adding callback functions to a queue of tasks to be executed when the callstack is empty.
  • In order to handle errors during asynchronous operations, provided callback functions are expected to have an error as their first parameter.
    Node allows for both output, data/feedback to a user provided by a computer, and input data/feedback to the computer provided by the user.
  • The Node fs core module is an API for interacting with the file system.
    Streams allow us to read or write data piece by piece instead of all at once.
  • The Node http core module allows for easy creation of web servers, computer processes that listen for requests from clients and return responses.

Woah, that was a lot… And there’s even more to Node that we didn’t cover in this lesson, but don’t panic! Learning Node isn’t about memorizing every aspect of the environment. The best way to get comfortable with Node is just to practice making things in it. Your imagination is the limit! If you haven’t already, download Node on your local machine. You can start by recreating some of the programs you built in this lesson— put your own spin on a guessing game, for example. If you’re eager to build web application back-ends, we recommend you start learning the awesome Express.js web framework.

Learning Express!

Question

what is port?

Answer

Ports are just endpoints of communication, simply put a location where a user or request will arrive. Taking the nautical idea of a seaport, the request will always dock (arrive) at the pointed location.

Ports allow us to identify where our application connection exists. We mainly see it as http://localhost:XXXX. where the exes are numbers that characterize the port. Localhost simply stands for our local machine’s ip adress to be recognized by the http protocol, and so the port number will be the location entry point for our server or app’s server.

So, when we declare in express:

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

we are creating a variable (cleverly called port, so we know what it stands for), and assign either our machine’s predefined port or the number 4001, this number can be anything between 1 and 65535, where the most common numbers used for local servers are: 8080, 4000, and 3000 (commonly used by React apps).

8080 is just the default choice typically used for a personally hosted web server, in the case of why 4001 is also commonly chosen, it has to do with network connectivity port codes, this number is commonly related to connections that guarantee the delivery of data in the same order that the request sent was received.

Now that we have a better idea of what a port is, in express we just need to write:

app.listen(PORT, () => { ...})

passing the port as the first parameter of the listen() method to assign that port number to our localhost server so we can have a location to communicate with app.

Starting A Server

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;
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’.

Writing Your First Route

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.

Sending A Response

HTTP follows a one request-one response cycle. Each client expects exactly one response per request, and each server should only send a single response back to the client per request. The client is like a customer at a restaurant ordering a large bowl of soup: the request is sent through the wait staff, the kitchen prepares the soup, and after is it prepared, the wait staff returns it to the customer. In the restaurant, it would be unfortunate if the soup never arrived back to the customer, but it would be equally problematic if the customer was given four large bowls of soup and was asked to consume them all at the exact same time. That’s impossible with only two hands!

Express servers send responses using the .send() method on the response object. .send() will take any input and include it in the response body.

const monsters = [{ type: 'werewolf' }, { type: 'hydra' }, { type: 'chupacabra' }];
app.get('/monsters', (req, res, next) => {res.send(monsters);
});

In this example, a GET /monsters request will match the route, Express will call the callback function, and the res.send() method will send back an array of spooky monsters.

In addition to .send(), .json() can be used to explicitly send JSON-formatted responses. .json() sends any JavaScript object passed into it.

Matching Route Paths

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

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.

Getting A Single Expression

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 /monster/: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.

const monsters = { hydra: { height: 3, age: 4 }, dragon: { height: 200, age: 350 } };
// GET /monsters/hydra
app.get('/monsters/:name', (req, res, next) => {console.log(req.params) // { name: 'hydra' };res.send(monsters[req.params.name]);
});

In this code snippet, a .get() route is defined to match /monster/:name path. When a GET request arrives for /monster/hydra, the callback is called. Inside the callback, req.param is an object with the key name and the value hydra, which was present in the actual request path. The appropriate monster is retrieved by its name from the monsters object and sent back to the client.

Setting Status Codes

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.

The res object has a .status() method to allow us to set the status code, and other methods like .send() can be chained from it.

const monsterStoreInventory = { fenrirs: 4, banshees: 1, jerseyDevils: 4, krakens: 3 };
app.get('/monsters-inventory/:name', (req, res, next) => {const monsterInventory = monsterStoreInventory[req.params.name];if (monsterInventory) {res.send(monsterInventory);} else {res.status(404).send('Monster not found');}
});

In this example, we’ve implemented a route to retrieve inventory levels from a Monster Store. Inventory levels are kept in the monsterStoreInventory variable. When a request arrives for /monsters-inventory/mothMen, the route matches and so the callback is invoked. req.params.name will be equal to ‘mothMen’ and so our program accesses monsterStoreInventory[‘mothMen’]. Since there are no mothMen in our inventory,res.status() sets a 404 status code on the response, and .send() sends the response.

Matching Longer Paths

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.

Other HTTP Methods

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.

Using Queries

You may have noticed in the previous exercise that our PUT route had no information about how to update the specified expression, just the id of which expression to update. It turns out that there was more information in the request in the form of a query string. Query strings appear at the end of the path in URLs, and they are indicated with a ? character. For instance, in /monsters/1?name=chimera&age=1, the query string is name=chimera&age=1 and the path is /monsters/1/

Query strings do not count as part of the route path. Instead, the Express server parses them into a JavaScript object and attaches it to the request body as the value of req.query. The key: value relationship is indicated by the = character in a query string, and key-value pairs are separated by &. In the above example route, the req.query object would be { name: 'chimera', age: '1' }.

const monsters = { '1': { name: 'cerberus', age: '4'  } };
// PUT /monsters/1?name=chimera&age=1
app.put('/monsters/:id', (req, res, next) => {const monsterUpdates = req.query;monsters[req.params.id] = monsterUpdates;res.send(monsters[req.params.id]);
});

Here, we have a route for updating monsters by ID. When a PUT /monsters/1?name=chimera&age=1 request arrives, our callback function is called and, we create a monsterUpdates variable to store req.query. Since req.params.id is ‘1’, we replace monsters[‘1’]‘s value with monsterUpdates . Finally, Express sends back the new monsters[‘1’].

When updating, many servers will send back the updated resource after the updates are applied so that the client has the exact same version of the resource as the server and database.

Little Quiz

Use req.query to update the proper element in the expressions array.

We’ve imported a helper function from /utils.js to help with this task.

You can use the updateElement() helper function in your PUT /expressions/:id route.

It takes three arguments:

id (the ID number of the element)
queryArguments (the new, updated expression object from req.query)
elementList (the array which contains the element to update)
updateElement() updates that specific element in the elementList array (you’ll pass in the expressions array), and then returns the updated element.

Be sure to check that an expression with the id you provide exists in the expressions array (getIndexById() can help)!

To test your functionality with the Express Yourself machine, make sure your server is running, get all expressions, and then use the UPDATE tab to select an individual expression, select updates, and send the PUT request.

const express = require('express');
const app = express();// Serves Express Yourself website
app.use(express.static('public'));const { getElementById, getIndexById, updateElement,seedElements, createElement } = require('./utils');const expressions = [];
seedElements(expressions, 'expressions');const PORT = process.env.PORT || 4001;
// Use static server to serve the Express Yourself Website
app.use(express.static('public'));app.get('/expressions', (req, res, next) => {res.send(expressions);
});app.get('/expressions/:id', (req, res, next) => {const foundExpression = getElementById(req.params.id, expressions);if (foundExpression) {res.send(foundExpression);} else {res.status(404).send();}
});app.put('/expressions/:id', (req, res, next) => {const expressionIndex = getIndexById(req.params.id, expressions);if (expressionIndex !== -1) {updateElement(req.params.id, req.query, expressions);res.send(expressions[expressionIndex]);} else {res.status(404).send();}
});app.listen(PORT, () => {console.log(`Listening on port ${PORT}`);
});

Matching By HTTP Verb

Express matches routes using both path and HTTP method verb. In the diagram to the below, 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.

Creating An Expression

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 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 /monster/:id does not make sense because a client could not 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.

Little Quiz

Create a POST /expressions route. It should send create and add a new expression to the expressions array if it is a valid new expression (meaning it has an emoji and name key). It should send back the new element with a 201 status code if it is valid, and it should send a 400 status code if the object is not valid.

You can use the createElement(elementType, objectToCreate) helper function to create a valid expression. The first argument is the type of element, so it should be ‘expressions’ in this case. The second argument should be the query object with an emoji and a name property. This function will return false if the objectToCreate does not contain all necessary key-value pairs, and it will return the newly-created element if object to create is valid. It does not add the created element to any arrays, you will need to do so yourself.

Don’t forget to restart your server and test as you implement the functionality. To test your route, use the POST tab in the upper left corner. Select a name and emoji and send the request to see if your route works as intended.

const express = require('express');
const app = express();// Serves Express Yourself website
app.use(express.static('public'));const { getElementById, getIndexById, updateElement,seedElements, createElement } = require('./utils');const expressions = [];
seedElements(expressions, 'expressions');const PORT = process.env.PORT || 4001;
// Use static server to serve the Express Yourself Website
app.use(express.static('public'));app.get('/expressions', (req, res, next) => {res.send(expressions);
});app.get('/expressions/:id', (req, res, next) => {const foundExpression = getElementById(req.params.id, expressions);if (foundExpression) {res.send(foundExpression);} else {res.status(404).send();}
});app.put('/expressions/:id', (req, res, next) => {const expressionIndex = getIndexById(req.params.id, expressions);if (expressionIndex !== -1) {updateElement(req.params.id, req.query, expressions);res.send(expressions[expressionIndex]);} else {res.status(404).send();}
});app.post('/expressions', (req, res, next) => {const receivedExpression = createElement('expressions', req.query);if (receivedExpression) {expressions.push(receivedExpression);res.status(201).send(receivedExpression);} else {res.status(400).send();}
});app.listen(PORT, () => {console.log(`Listening on port ${PORT}`);
});

Deleting Old Expressions

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.

Little Quiz

1.Create a DELETE /expressions/:id route. It should send back a 404 response for a request with an invalid id, and it should delete the proper element from the expressions array and send a 204 status with a valid id. To test your functionality, use the DELETE tab in the upper left. Select the ID to delete and send the request.

const express = require('express');
const app = express();// Serves Express Yourself website
app.use(express.static('public'));const { getElementById, getIndexById, updateElement,seedElements, createElement } = require('./utils');const expressions = [];
seedElements(expressions, 'expressions');
const animals = [];
seedElements(animals, 'animals');const PORT = process.env.PORT || 4001;
// Use static server to serve the Express Yourself Website
app.use(express.static('public'));app.get('/expressions', (req, res, next) => {res.send(expressions);
});app.get('/expressions/:id', (req, res, next) => {const foundExpression = getElementById(req.params.id, expressions);if (foundExpression) {res.send(foundExpression);} else {res.status(404).send();}
});app.put('/expressions/:id', (req, res, next) => {const expressionIndex = getIndexById(req.params.id, expressions);if (expressionIndex !== -1) {updateElement(req.params.id, req.query, expressions);res.send(expressions[expressionIndex]);} else {res.status(404).send();}
});app.post('/expressions', (req, res, next) => {const receivedExpression = createElement('expressions', req.query);if (receivedExpression) {expressions.push(receivedExpression);res.status(201).send(receivedExpression);} else {res.status(400).send();}
});app.delete('/expressions/:id', (req, res, next) => {const expressionIndex = getIndexById(req.params.id, expressions);if (expressionIndex !== -1) {expressions.splice(expressionIndex, 1);res.status(204).send();} else {res.status(404).send();}
});app.listen(PORT, () => {console.log(`Listening on port ${PORT}`);
});
// The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
let arrDeletedItems = array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

Learn Express Routers

This File Is Too Big!

When our file is getting quite long and hard to read. It is easy to imagine that as we add functionality to an application, this file would get long and cumbersome.

Luckily, Express provides functionality to alleviate this problem: Routers. Routers are mini versions of Express applications —— they provide functionality for handling route matching, requests, and sending responses, but they do not start a separate server or listen on their own ports. Routers use all the .get(), .put(), .post(), and .delete() routes that you know and love.

In this lesson, we will use Routers to clean up our code and separate our application into a file to handle all /expressions routes and another to handle all .animals routes.

Express.Router

An Express router provides a subset of Express methods. To create an instance of one, we invoke the .Router() method on the top-level Express import.

To use a router, we mount it at a certain path using app.use() and pass in the router as the second argument. This router will now be used for all paths that begin with that path segment. To create a router to handle all requests beginning with /monsters, the code would like this:

const express = require('express');
const app = express();const monsters = {'1': {name: 'godzilla', age: 250000000}, '2': {name: 'manticore', age: 21}}const monstersRouter = express.Router();app.use('/monsters'. monstersRouter);monstersRouter.get('/:id', (req, res, next) => {const monster = monsters[req.params.id];
if (monster) {res.send(monster);} else {res.status(404).send();
}
});

Inside the monstersRouter, all matching routes are assumed to have /monsters prepended, as it is mounted at that path. monstersRouter,get(’/:id’) matches the full path /monster/:id.

When a GET /monsters/1 request arrives, Express matches /monsters in app.use() because the beginning of the path ('/monsters') matches. Express’ route-matching algorithm enters the monstersRouter‘s routes to search for full path matches. Since monstersRouter.get('/:id) is mounted at /monsters, the two paths together match the entire request path (/monsters/1), so the route matches and the callback is invoked. The ‘godzilla’ monster is fetched from the monsters array and sent back.

Using Multiple Router Files

We will keep each each router in its own file, and require them in the main application. This allows us to keep our code clean and our files short.

To do this with monstersRouter, we would create a new file monsters.js and move all code related to /monsters requests into it.

const express = require('express');
const monstersRouter = express.Router();const monsters = {'1': {name: 'godzilla',age: 250000000},'2': {Name: 'manticore',age: 21}
}monstersRouter.get('/:id', (req, res, next) => {const monster = monsters[req.params.id];if (monster) {res.send(monster);} else {res.status(404).send();}
});module.exports = monstersRouter;

This code contains all the monsters specific code. In a more full-fledged API, this file would contain multiple routes. To use this router in another file, we use module.exports so that other files can access monstersRouter. The only other new line of code required is that Express must be required in each file, since we’ll need to create a router with express.Router().

Our main.js file could then be refactored to import the monstersRouter:

// main.js
const express = require('express');
const app = express();
const monsterRouter = require('./monster.js');app.use('/monsters', monstersRouter);

Matching In Nested Routers

As you saw in the previous exercise, when using routers, it is important to remember that the full path of a request can be segmented.

In the diagram to the left, you can create an Express application using two routers. A GET request arrives for /expressions/1. Because the beginning of the path does not match /animals in the first app.use(), the Express server moves on to the next app.use(), which matches /expressions.

Express’ route matching algorithm then enters the expressionRouter instance which is required from expression.js. Inside this router, the path matching changes. Even though the whole request path is /expressions/1, inside the expressionsRouter, all paths are matched from the parts of the path after /expressioms, meaning that in this context, the router is trying to match the path /1.

Because the path is /1, the path does not match the first .get() method at /. The Express server moves on to the next route, which has a route parameter of /:id, so it matches! This route handle the necessary logic and sends the response.

Routers can be nested as many times as necessary for an application, so understanding nested route matching is important for created complicate APIs.

Refactoring Expressions Routes

Now that you’ve learned about nested route matching, let’s refactor the rest of the /expressions routes into expressions.js.

Move all your /expressions routes to your router into expressions.js. Make sure that they still match the same request paths, and remove the duplicate code from app.js.

Move the following routes to expresions.js:

  • GET /expressions/:id
  • PUT /expressions/:id
  • POST /expressions
  • DELETE /expressions/:id
    Remember to change the paths for each route handler as you move them, as they should already be mounted at /expressions inside expressions.js.

Make sure that you still require the same helper functions from utils.js in expressions.js.

How does require() work and why I do not need a relative path for express?

Question: How does require() work and why I do not need a file path for express?

Answer
As we may remember, require() is a node method, Node being a runtime-environment (think of an emulator仿真器 for the browser’s javascript functionality), by itself node is also comprised of multiple libraries and it has its own existing methods.

So we can use require() anywhere while working within Node. We know that we are working with Node when we create an app environment using npm and creating a package.json file.

Now, Node has a very specifically structured module system, each module is a library that helps us run node or helps us build on top of the existing environment (like we do when we npm install express from the command line). It is because of that structure that node has already in memory the location where the node models need to be and are.

Based on that knowledge, the require() method only needs a file name (identification) that will differentiate one module from others, in the case of express, since it is a library module require() will know that it is in the node modules directory and so it only needs the identification (aka the string ‘express’), versus trying to require a local file that we have created, which in that case we need a relative file path.

In itself the require() method checks if the string passed is a relative path or not, if it is not, it will use the node modules directory to find a matching expression to the passing string and retrieve it. If it is a file path though, it will check if it has a file extension, here, not having one is like knowing that it is a JavaScript file (.js) and then it will retrieve its value from the given path and return it.

Express知识详解

req.params

This property is an object containing properties mapped to the name route “parameters”. For example, if you have the route /user/:name, then the “name” property is available as req.params.name. This object defaults to { }.

// GET /user/tj
console.dir(req.params.name)
// => 'tj'

When you use a regular expression for the route definition, capture groups are provided in the array using req.params[n], where n is the nth capture group. This rule is applied to unnamed wild card matches with string routes such as /file/*:

// GET /file/javascripts/jquery.js
console.die(req.params[0])
// => 'javascripts/jquery.js'

If you need to make changes to a key in req.params, use the app.param handler. Changes are applicable only to parameters already defined in the route path.
Any changes made to the req.params object in a middleware or route handler will be reset.

req.query

This property is an objects containing a property for each query string parameter in the route, When query parser is set to disabled, it is an empty object{}, otherwise it is the result of the configured query parser.

// GET /search?q=tobi+ferret
console.dir(req.query.q)
// => 'tobi ferret'// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
console.dir(req.query.order)
// => 'desc'console.dir(req.query.shoe.color)
// => 'blue'console.dir(req.query.shoe.type)
// => 'converse'// GET /shoes?color[]=blue&color[]=black&color[]=red
console.dir(req.query.color)
// => ['blue', 'black', 'red']

Is there an order for how to write a router and use it in app?

Answer

Most pieces for an express server are interchangeable in order, that is one of the wonderful advantages of that library, but we do want to keep a few things in mind.

  • We do want to make sure the router has been declared before we set up any requests and before we implement app.use(), ie:
const routesRouter = express.Router();routesRouter.get('/', (req,res,next) => {...});
... //other methodsapp.use('routes', routesRouter);

or

const routesRouter = express.Router();app.use('routes', routesRouter);routesRouter.get('/', (req,res,next) => {...});
... //other methods

even if in a different file:

//in routesRouter.js
const express = require('express');const routesRouter = express.Router();routesRouter.get('/', (req,res,next) => {...});
... //other methods
module.exports = routesRouter;

Now in app:

...
const routesRouter = require('./routesRouter');app.use(routesRouter);
...
  • We also want to make sure we declare it before any request handler, now the handlers can be written before or after app.use() if they are in the same file, but because we need to have the router declared to use it, we cannot start writing, the router is created to handle routers, and at last, we open the server to requests with the listen method.

{color: ‘green’; age: ‘4’, eyes: ‘2’ }



Learn Express

“Correct” code can take so many different forms, developers have cultural notions of code quality that is somewhat independent of these decisions.

One concept that is central to the notion of quality code is that all code is read many, many more times than it is written. Maintaining and updating code takes up much more of a software developer’s time than production. There are many ways to make this less of a burden, and these techniques frequently correspond to code quality principles. Naming variables consistently so that they are identifiable is one way to improve the readability of a codebase. Another pillar of code quality is avoiding duplication of code within a codebase.

Code duplication is an invitation for bugs. If incorrect code is copy-and-pasted in multiple places, a developer might remedy the flaws in only a few of those places and fail to fix the buggy code everywhere. In this course, we will investigate several ways to avoid replication and reduce complexity. In programming in general, this often means putting the reused code into reusable containers like functions and objects. In Express specifically, this will also mean composing our desired functionality into a series of middleware functions.

DRYing Code With Functions

Beyond labeling, good code will leverage the strength of its programming language to avoid performing the same tasks
Take a look at the following code:

const addFive = number => {const fiveAdded = number + 5;console.log(`Your number plus 5 is ${fiveAdded}`);
}const addTen = number => {const tenAdded = number + 10;console.log(`Your number plus 10 is ${tenAdded}`);
}const addTwenty = number => {const twentyAdded = number + 20;console.log(`Your number plus 20 is ${twentyAdded}`);
}

The code above defines three different functions that accomplish the radically different tasks of: adding five to a number and logging the sum, adding ten to a number and logging the sum, and adding twenty to a number and logging the sum. While these three function definitions are not exact duplicates of each other, a well-designed application will be flexible enough to join similar functionality in a single element.

const addNumber = (number, addend) => {const numAdded = number + addend;console.log(`Your number plus ${addend} is ${numAdded}`);
}

As you can see, by adding an argument to the earlier functions we can simplify our application code which will ultimately save time should we realize that we also want an addFifty() function and an addHundred() function. Code that performs the same task in multiple places is repetitive, and the quality coder’s credo is “Don’t Repeat Yourself” (DRY). If a program performs similar tasks without refactoring into a function, it is said to “violate DRY”. “Violating DRY” is a programmer’s way of complaining: “This script is saying the same thing over and over! We can do the same thing with less code!” Let’s try to not repeat ourselves in this codebase by repurposing some of the more glaringly repeated code into functions we can call instead.

DRYing Routes With app.use()

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 is not related to midddleware. 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

next()

It seems like our middleware was successful - it logged out.

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 is important to note that this is regardless of method - app.use() that occurs after an app.get() will get called after the app.get(). Observe the following code:

app.use((req, res, next) => {console.log("A sorcerer approaches!");next();
});app.get('/magic/:spellname', (req, res, next) => {console.log("The sorcerer is casting a spell!");next();
})app.get('/magic/:spellname', (req, res, next) => {console.log(`The sorcerer has cast ${req.params.spellname}`);res.status(200).send();
});app.get('/magic/:spellname', (req, res, next) => {console.log("The sorcerer is leaving!");
});

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 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.

Request And Response Parameters

Recall the function signature of an Express middleware, i.e., (req, res, next). Express routes are middleware. Every route created in Express is also a middleware function handling the request and response objects at the part of stack. Express routes also have the option of sending a response body and status code and closing the connection. These two features are a byproduct of Express routes being middleware, because all Express middleware functions have access to the request, the response, and the next middleware in the stack.

Rout-Level app.use() -Single Path

In documentation for many programming languages, optional arguments for functions are placed in square brackets ([]). This means that app.use() takes an optional path parameter as its first argument. We can now write middleware that will run for every request at a specific path.

app.use('/sorcerer', (req, res, next) => {console.log('User has hit endpoint /sorcerer');next();
});

In the example above the console will print ‘User has hit endpoint /sorcerer’, if someone visits our web page’s ‘/sorcerer’ endpoint. Since the method app.use() was used, it won’t matter if the user is performing a GET,a POST, or any other kind of HTTP request. Since the path was given as an argument to app.use(), this middleware function will not execute if the user hits a different path (for instance: ‘/spells’ or ‘/sorcerer/:sorcerer_id’).

【NodeJS】Codecademy学习笔记相关推荐

  1. Web全栈架构师(三)——NodeJS+持久化学习笔记(2)

    NodeJS+持久化学习笔记 持久化 nodejs中实现持久化的方法 文件系统数据库 MySQL 资源 安装配置 node.js原生驱动 Sequelize 基本使用: Getters & S ...

  2. NodeJS入门——学习笔记

    之前有段时间闲着无聊,去B站上简单了解了一下NodeJS,下面是根据一个视频记录的笔记,供大家参考,错误之处请大家帮忙斧正.侵删. 目录 0 参考教程 1 NodeJs简介 1.1 官网介绍:http ...

  3. nodejs入门学习笔记一——一个完整的http路由服务实现

    开始学习nodejs! 参考书籍:The Node Beginner Book ,所有问题和讨论都围绕本书. 1.学习nodejs需要具备的基础知识: js基本语法,基本上写过前端的都能满足,原生js ...

  4. nodejs基础学习笔记

    进入命令窗口之后,输入:ipconfig/all 回车即可看到整个电脑的详细的IP配置信息:IPv4地址 url网址解析 (慕课网http://www.imooc.com/video/6710/0) ...

  5. NodeJS学习笔记: RESTful —— 为本系列做个小结

    前言 本人不是技术专家,该笔记只是从使用语言进行开发的层面上记录一些体会,不包含也不想尝试从源码或者更深的层次去讨论语言本身的优劣.文章内容是笔者的个人感悟,既不保证正确性,也不保证别人能看懂. 这是 ...

  6. 好程序员web前端分享Nodejs学习笔记之Stream模块

    好程序员web前端分享Nodejs学习笔记之Stream模块 一,开篇分析 流是一个抽象接口,被 Node 中的很多对象所实现.比如对一个 HTTP 服务器的请求是一个流,stdout 也是一个流.流 ...

  7. Nodejs学习笔记(七)——接口API

    [目录] Nodejs学习笔记(一)--基础之全局对象.包和npm Nodejs学习笔记(二)--模块 Nodejs学习笔记(三)--同步和与异步之文件系统模块 Nodejs学习笔记(四)--http ...

  8. Nodejs学习笔记(六)——Mysql模块

    [目录] Nodejs学习笔记(一)--基础之全局对象.包和npm Nodejs学习笔记(二)--模块 Nodejs学习笔记(三)--同步和与异步之文件系统模块 Nodejs学习笔记(四)--http ...

  9. Nodejs学习笔记(四)——http协议与服务器

    [目录] Nodejs学习笔记(一)--基础之全局对象.包和npm Nodejs学习笔记(二)--模块 Nodejs学习笔记(三)--同步和与异步之文件系统模块 Nodejs学习笔记(五)--expr ...

  10. Nodejs学习笔记(二)——模块

    [目录] Nodejs学习笔记(一)--基础之全局对象.包和npm Nodejs学习笔记(三)--同步和与异步之文件系统模块 Nodejs学习笔记(四)--http协议与服务器 Nodejs学习笔记( ...

最新文章

  1. R语言可视化分面图、假设检验、单变量分组多水平t检验并指定参考水平、可视化单变量分组多水平分面箱图(faceting boxplot)并添加显著性水平、指定显著性参考水平
  2. 转[WinForm] VS2010发布、打包安装程序(超全超详细)
  3. Linux keypad 设备树,SC7731客户配置文档.pdf
  4. java interface 实例_Java - Interface 接口的实现方式实例
  5. 阿里云获工信部CDN业务经营许可 云计算业内资质最全
  6. 我学到的5件事,指导2,500名有抱负的开发人员
  7. python学习day07-encode和decode
  8. 随想录(项目管理中的感受)
  9. 软件设计师10-面向对象-设计模式
  10. buffer正确的拼接方式
  11. 用python做计算器
  12. 自动化技术、计算机技术核心期刊整理及介绍
  13. 类库、框架、模块、组件等概念介绍
  14. 计算机专业在线作图工具
  15. 北京东方国信科技有限公司
  16. tar解压tgz文件出现gzip: stdin: not in gzip format报错问题
  17. 基于TCAM 的高速路由查找
  18. 蓝桥杯单片机设计与开发笔记(一)
  19. UE4 3D场景实现双向箭头绘制
  20. 【C语言】例3.5 求方程的根

热门文章

  1. filco蓝牙不好用_FILCO蓝牙机械键盘,超稳连接6米开外不掉线
  2. 计算机应用基础2004版,计算机应用基础试题2004年6月
  3. 读后感:救黑熊重要,还是救助失学儿童重要?
  4. 数据库查询-分数排名
  5. RK987蓝牙键盘使用说明书分享
  6. 当AI能气味编程时,网友:这才是真正的黑客帝国!
  7. 2020软科中国计算机学科排名,【智库数据】基于2020软科中国最好学科排名的学科动态发展跟踪...
  8. Flutter开发之常用Widget学习
  9. x的x分之一次方极限x趋于0_e的x分之一的左右极限
  10. 面试测试工程师遇到的面试题——非技术方面