I've learned a lot of new things since my last post - sorry for the delay. But, most notable is Node.js. I'm really growing to enjoy JavaScript, even though before I said I like Python more. Python's interpreted nature makes for a quick, engaging development experience, but JavaScript has the same virtue! Additionally, with JavaScript because it runs alongside CSS and HTML, it is much easier to make a UI.
I've also learned about Node.js, which is an awesome way to run JavaScript outside of the browser. Express.js is a module for Node.js that allows you to make a REST API for your web app using JavaScript.
Running JavaScript in Node.js is very similar to running it in the browser. The main difference is the types of programs you're writing. Node.js allows you to create a backend for your web apps using JavaScript.
Express.js is a great framework for creating a simple backend server, serving a RESTful API. REST stands for representational state transfer, and API stands for application programming interface. Together, a RESTful API transfers a representation of the state of the resources needed for building an application.
The main operations a RESTful API can do are GET, POST, PUT, and DELETE. These 4 operations are done when a client "requests" them to be, using a HTML Request.
Using Express.js, you start an instance of the express server, by running
const app = express();
Then for each of the requests, app can be assigned
a callback using a route method, for a specific path. That could look like
the following:
1// GET /data/id
2app.get("/data/:id", (req, res) => {
3 // get the id from the query parameters.
4 const id = req.params.id;
5 // look up data somehow. You want to make sure it exists.
6 if (dataArray.findIndex(id) !== -1) {
7 const data = dataArray[id];
8 // send the data back to the client in the response.
9 res.status(200).send(data);
10 } else {
11 // if the data doesn't exist, tell the client.
12 res.status(404).send();
13 }
14});
15
16// POST /data
17app.post("/data", (req, res) => {
18 // get the data from the request body.
19 const data = req.body;
20 // make sure the data is valid, then add the data entry.
21 if (validData(data)) {
22 dataArray.push(data);
23 // tell the client that an entry was create, and what it was.
24 res.status(201).send(data);
25 } else {
26 // if the data was invalid, tell the client it was rejected.
27 res.status(403).send();
28 }
29});
30
31// PUT /data/id
32app.put("/data/:id", (req, res) => {
33 // get the id and data.
34 const id = req.params.id;
35 const data = req.body;
36 // check to see if the entry doesn't exist and tell the client.
37 if (dataArray.findIndex(id) === -1) {
38 res.status(404).send();
39 // check to see if the data is invalid and tell the client.
40 } else if (!validData(data)) {
41 res.status(403).send();
42 // data must be valid, so update it.
43 } else {
44 dataArray[id] = data;
45 // tell the client that the entry was updated.
46 res.status(204).send();
47 }
48});
49
50// DELETE /data/id
51app.delete("/data/:id", (req, res) => {
52 // get the id.
53 const id = req.params.id;
54 // check to see if the entry exists, and delete it.
55 if (dataArray.findIndex(id) !== -1) {
56 dataArray.splice(id, 1);
57 // tell the client the entry was deleted.
58 res.status(204).send();
59 } else {
60 // if the entry didn't exist, tell the client.
61 res.status(404).send();
62 }
63});
When a request is made, if it's type and path matches any of the route
methods, then that method's callback is executed. The path is specified using
the string passed to the route method. You can also pass an array of multiple
strings, or use regex to select match paths. In the example GET, PUT, and
DELETE all had the same path '/data/:id'
. Because they all were different
request types though, each request will only correspond to one of them.
The /:id
part is significant. This lets you match a parameter in the path.
If you were to make a request 'GET /data/87' then it would set id
to 87.
The callback has parameters req which represents the request, and res which
represents the response. You can pass more parameters, like (req, res, next)
or (err, req, res, next)
to add more functionality. err represents an error
thrown while handling a request. You should put a route method that handles
errors at the bottom. This is important, because Express.js can handle
multiple matching route methods, but it executes them in the order they are
declared in.
The other important callback parameter is next which represents the next
route method declared that matches. It is a function itself, so you can just
pass the error to it, as in next(Error)
.
app.param()
and app.use()
Errors aren't the only use of next though. You can also use it to DRY your
code, or "Don't Repeat Yourself." You probably noticed, much of the route
methods from before were very similar, so in order to limit repetition, you
can use app.param()
or app.use()
.
The app.param()
method lets you define parameters once, instead of for each
route method that uses that parameter. Here's an example of it's use:
1app.param("id", (req, res, next, id) => {
2 // get the id and set it as an attribute of req.
3 req.id = id;
4 // call the next route handler.
5 next();
6});
7
8app.get("/data/:id", (req, res, next) => {
9 // look up data and make sure it exists.
10 if (dataArray.findIndex(req.id) !== -1) {
11 const data = dataArray[req.id];
12 // send the data back to the client in the response.
13 res.status(200).send(data);
14 } else {
15 // if the data doesn't exist, tell the client.
16 res.status(404).send();
17 } // call the next route handler.
18 next();
19});
Now for every route method that uses the id query parameter, you don't have to redefine it! This helps avoid errors in your code, since there isn't repeated blocks of code that all must be updated individually.
app.use()
is used just like every other route handler, but it matches any
kind of request. If you wanted to use the example just given with param and
get, you might add an app.use()
to perform logging for all requests at the
end of your file, but before error handling. Here's an example:
1app.use("/data/:id", (req, res, next) => {
2 // log the request method and current time.
3 console.log(`Recieved ${req.method} request at ${Date()}.`);
4 // call the next route handler.
5 next();
6});