Codeburst.io /the-only-no… Abstract: This article is for beginners who have little or no knowledge of Node. It covers HTTP modules, Express, mongodb and RESTful apis in a comprehensive but not in-depth manner.
If you’re a front-end developer, writing web applications based on NodeJS is nothing new to you. Both NodeJS and Web applications rely heavily on JavaScript.
First, realize that Node is not a silver bullet. That said, it’s not the best solution for every project. Anyone can create a server based on Node, but this requires a deep understanding of the language in which web applications are written.
Recently, I’ve had a lot of fun learning Node, and I’ve realized that I already have some knowledge that I need to share and get feedback from the community to improve myself.
So let’s get started.
Before Node.js came along
Before Node.js, Web applications tended to be based on the client/server model. When the client requested resources from the server, the server would respond to the request and return the corresponding resources. The server responds only when it receives a client request and closes the connection to the client when the response is complete.
This design pattern requires consideration of efficiency, as each request requires processing time and resources. Therefore, the server should close the connection each time it processes the requested resource in order to respond to other requests.
What happens to a server when thousands of requests are sent to it at the same time? When you ask this question, you don’t want a request to have to wait for other requests to be answered before it can be turned on, because this delay is too long.
Imagine trying to open FaceBook, but you have to wait five minutes to see the content because thousands of people have sent requests to the server before you. Is there a solution to handle hundreds or thousands of requests at once? Fortunately, we have the threading tool.
Threading is the way a system can process multiple tasks in parallel. Each request to the server starts a new thread, and each thread gets everything it needs to run the code.
Sounds weird, right? Let’s take a look at this example:
Imagine a restaurant with only one chef serving food, and as the demand for food grows, things get worse and worse. People had to wait a long time before all the previous orders were processed. And the only way we can think of is to add more waiters to solve that problem, right? So you can handle more customers at once.
Each thread is a new server, and the customer is the browser. I don’t think it’s difficult for you to understand that.
A side effect of this system, however, is that when the number of requests reaches a certain level, too many threads will consume all system memory and resources. To go back to our example, hiring more and more people to serve food inevitably increases labor costs and takes up more kitchen space.
Of course, it would be great for us if the server immediately disconnects and frees up all resources after responding to the client’s request.
Multithreaded systems excel at CPU intensive operations because they require a lot of logic to process and take more time to compute. If every request is processed by a new thread, the main thread can be freed up to handle some important calculations, which also makes the whole system faster.
Keeping the main thread from being busy with all the operations is a good way to improve efficiency, but what about taking it a step further?
NodeJS has come
Imagine that we now have a multi-threaded server running in a Ruby on Rails environment. We need it to read the file and send it to the browser that requests it. The first thing to know is that Ruby does not read the file directly, but tells the file system to read the specified file and return its contents. As the name implies, a file system is a program on a computer that is designed to access files.
Ruby notifies the file system and waits for it to finish reading the file, rather than moving on to other tasks. When the file system processing is complete, Ruby will restart to collect the file contents and send them to the browser.
This approach obviously leads to blocking, and NodeJS was created to address this pain point. If we use Node to notify the file system, Node will process other requests while the file system reads the file. After reading the file, the file system tells Node to read the resource and return it to the browser. In fact, the internal implementation relies on Node’s event loop.
The core of Node is JavaScript and event loops.
Simply put, an event loop is a program that waits for events and then fires them when needed. It’s also important to note that Node, like JavaScript, is single-threaded.
Remember the restaurant example we used? Node’s restaurants always have a single chef cooking food, regardless of the number of customers.
Unlike other languages, NodeJS does not need to start a new thread for each request, it receives all requests and then delegates most of the work to other systems. Libuv is a library that relies on the OS kernel to efficiently handle these tasks. When these behind-the-scenes workers have finished processing the events entrusted to them, they trigger callbacks bound to those events to notify NodeJS.
Here we get to the concept of a callback. Callbacks are not difficult to understand; they are functions that are passed as arguments by other functions and are called under certain circumstances.
NodeJS developers mostly write event handlers that are called after a particular NodeJS event has occurred.
NodeJS is single-threaded, but it is much faster than multithreaded systems. That’s because programs tend to be more than just long math and logic. They spend most of their time writing files, handling network requests, or requesting permissions from consoles and peripherals. These are the kinds of problems NodeJS is good at dealing with: when NodeJS is working on these things, it quickly delegates these events to a specialized system and moves on to the next event.
If you dig a little deeper, you might realize that NodeJS is not very good at handling CPU-consuming operations. This is because CPU intensive operations consume a lot of main thread resources. Ideally for a single-threaded system, these operations are avoided to free up the main thread to do something else.
Another key point is that in JavaScript, only the code you write is not executed concurrently. That is, your code can only handle one thing at a time, while other workers, such as file systems, can handle their work in parallel.
If that doesn’t make sense to you, here’s an example:
Once upon a time there was a king who had a thousand officials. The king wrote a list of tasks for his officials to do, and the list was very, very, very long. There was a prime minister who delegated tasks to all the other officials according to the list. Each time he completed a task, he reported his results to the king, who then gave him another list. Because while the officials were working, the king was busy writing other lists.
The point of this example is that even though there are many officials working in parallel, the king can only do one thing at a time. Here, the king is your code, and the official is the system worker behind NodeJS. So everything happens in parallel, except your code.
Well, let’s get on with the NodeJS journey.
Write a Web application using NodeJS
Writing a Web application with NodeJS is equivalent to writing event callbacks. Let’s take a look at the following example:
- Create and enter a folder
- perform
npm init
“Command until you create a package.json file at the root of the folder. - Create a new file named server.js and copy and paste the following code:
//server.js const http = require('http'), server = http.createServer(); server.on('request',(request,response)=>{ response.writeHead(200,{'Content-Type':'text/plain'}); response.write('Hello world'); response.end(); }); server.listen(3000,()=>{ console.log('Node server created at port 3000'); }); Copy the code
- On the command line, enter
node server.js
, you should see the following output:node server.js //Node server started at port 3000 Copy the code
Open your browser and go to Localhost :3000, and you should see a Hello World message.
First, we introduced the HTTP module. This module provides an interface for handling HTPP operations, and we call the createServer() method to create a server.
After that, we bind an event callback to the Request event, passing it to the second argument to the ON method. The callback function takes two arguments, request for the received request and Response for the data in the response.
We can ask Node to do more than just handle request events.
//server.js
const http = require('http'),
server = http.createServer((request,response)=>{
response.writeHead(200,{'Content-Type':'text/plain'});
response.write('Hello world');
response.end();
});
server.listen(3000,()=>{
console.log('Node server created at port 3000');
});
Copy the code
In the code, we pass a callback function to createServer(), which Node binds to the Request event. So we only care about the Request and response objects.
We use Response.writeHead () to set the return message header fields, such as status code and content type. Response.write () is to write to the Web page. Finally, we end the response with response.end().
Finally, we told the server to listen on port 3000 so that we could view a demo of our Web application while developing locally. The listen method requires that the second argument be a callback function that will be executed as soon as the server starts.
Custom callback
Node is a single-threaded event-driven environment, which means that everything in Node is a response to an event.
The previous example could be rewritten as follows:
//server.js
const http = require('http'),
makeServer = function (request,response){
response.writeHead(200, {'Content-Type':'text/plain'});
response.write('Hello world');
response.end();
},
server = http.createServer(makeServer);
server.listen(3000, () = > {console.log('Node server created at port 3000');
Copy the code
MakeServer is a callback function, and since JavaScript treats functions like first-class citizens, they can be passed to any variable or function. If you don’t already know JavaScript, you should take the time to understand what an event driver is.
When you start writing some serious JavaScript code, you may run into “callback hell.” Your code becomes difficult to read because of the large number of functions that are intertwined. This is when you want to find a more advanced and efficient way to replace callbacks. Check out Promise. Eric Elliott has written an article on what Promises are, which is a good primer.
NodeJS routing
A server can store a large number of files. When the browser sends a request, it tells the server what files they need, and the server returns the corresponding files to the client. This is called routing.
In NodeJS, we need to manually define our own routes. It doesn’t have to be a problem. Take a look at this basic example:
//server.js
const http = require('http'),
url = require('url'),
makeServer = function (request,response){
let path = url.parse(request.url).pathname;
console.log(path);
if(path === '/'){
response.writeHead(200, {'Content-Type':'text/plain'});
response.write('Hello world');
}
else if(path === '/about'){
response.writeHead(200, {'Content-Type':'text/plain'});
response.write('About page');
}
else if(path === '/blog'){
response.writeHead(200, {'Content-Type':'text/plain'});
response.write('Blog page');
}
else{
response.writeHead(404, {'Content-Type':'text/plain'});
response.write('Error page');
}
response.end();
},
server = http.createServer(makeServer);
server.listen(3000, () = > {console.log('Node server created at port 3000');
});
Copy the code
Paste this code and run it by typing the node server.js command. In the browser open localhost: 3000 and localhost: 3000 / abou, and then try to open the localhost: 3000 / somethingelse, should we jump to the error page?
While this satisfied our basic requirements for starting the server, it was crazy to have to code every single page on the server. No one actually does that, but this example just gives you an idea of how routing works.
If you noticed, we’ve introduced the URL module, which makes it a lot easier to work with urls.
Pass a URL string argument to the parse() method, which splits the URL into protocol, host, path, and QueryString. If you’re not familiar with these words, take a look at this chart below:
So when we execute url.parse(request.url).pathname, we get a URL pathname, or the URL itself. These are the requirements that we use to route requests. But there’s an easier way to do this.
Express is used for routing
If you’ve done your homework before, you’ve heard of Express. NodeJS is a NodeJS framework for building Web applications and apis, as well as writing NodeJS applications. Read on and you’ll see why I say it makes everything easier.
From your terminal or command line, go to the root directory of your computer and type NPM install Express –save to install the Express package. To use Express in a project, we need to introduce it.
const express = require('express');
Copy the code
Rejoice, life will get better.
Now, let’s use Express for basic routing.
//server.js
const express = require('express'),
server = express();
server.set('port', process.env.PORT || 3000);
//Basic routes
server.get('/', (request,response)=>{
response.send('Home page');
});
server.get('/about',(request,response)=>{
response.send('About page');
});
//Express error handling middleware
server.use((request,response) = >{
response.type('text/plain');
response.status(505);
response.send('Error page');
});
//Binding to a port
server.listen(3000, () = > {console.log('Express server started at port 3000');
});
Copy the code
The error status code is 505.
Does the code look cleaner now? I’m sure you can easily understand it.
First, when we introduce the Express module, we end up with a function. After calling this function, we can start our server.
Next, we use server.set() to set up the listening port. Process.env. PORT is set by the environment in which the application is run. Without this setting, we default it to 3000.
Then, if you look at the code above, you’ll see that the routes in Express all follow the same format:
server.VERB('route',callback);
Copy the code
VERB can be a GET or POST action, while PathName is a string following the domain name. Also, callback is the function that we want to fire when we receive a request.
Finally, we call server.listen(), remember what that does?
That’s the route in Node. Let’s explore how Node calls the database.
NodeJS database
A lot of people like to do everything in JavaScript. There are just a few databases that meet this requirement, such as MongoDB, CouchDB, and so on. These databases are NoSQL databases.
A NoSQL database is structured in the form of key-value pairs, which are document-based and do not store data in tables.
Let’s take a look at the MongoDB NoSQL database. If you have worked with relational databases such as MySQL and SQLserver, you should be familiar with the concepts of databases, tables, rows, and columns. MongoDB isn’t that different from them, but let’s compare.
Translator’s note: There should be a table showing the difference between MongoDB and MySQL, but it is not shown here.
To make the data more organized, we can use Mongoose to check the data type and add validation rules to the document before inserting data into MongoDB. It looks like a mediator between Mongo and Node.
Due to the length of this article, to keep each section as brief as possible, read the official MongoDB installation tutorial first.
In addition, Chris Sevilleja wrote Easily Develop Node.js and MongoDB Apps with Mongoose, which I think is a good basic tutorial for getting started.
Write RESTful apis using Node and Express
Apis are channels through which applications send data to other applications. Have you ever logged on to something that requires you to use your Facebook account? Facebook exposes certain functions to these sites, and these are apis.
A RESTful API should not change when the server/client state changes. By using a REST interface, different clients, even in different states, should perform the same actions and receive the same data when accessing the same REST terminal.
An API terminal is a function in an API that returns data.
Writing a RESTful API involves transferring data in JSON or XML format. Let’s try it in NodeJS. We’ll write an API that will return a dummy JSON data after the client initiates the request via AJAX. This is not an ideal API, but it helps to understand how it works in a Node environment.
- Create a folder called Node-api.
- Enter the folder from the command line and type
npm init
. This creates a dependency collection file; - The input
npm install --save express
To install Express; - Create 3 new files in the root directory:
server.js
.index.html
andusers.js
; - Copy the following code into the appropriate file:
//users.js
module.exports.users = [
{
name: 'Mark'.age : 19.occupation: 'Lawyer'.married : true.children : ['John'.'Edson'.'ruby'] {},name: 'Richard'.age : 27.occupation: 'Pilot'.married : false.children : ['Abel'] {},name: 'Levine'.age : 34.occupation: 'Singer'.married : false.children : ['John'.'Promise'] {},name: 'Endurance'.age : 45.occupation: 'Business man'.married : true.children : ['Mary']]},Copy the code
This is the data that we pass on to other applications, and we export this data to make it available to all applications. That is, we store the Users array in the Modules.exports object.
//server.js
const express = require('express'),
server = express(),
users = require('./users');
//setting the port.
server.set('port', process.env.PORT || 3000);
//Adding routes
server.get('/',(request,response)=>{
response.sendFile(__dirname + '/index.html');
});
server.get('/users',(request,response)=>{
response.json(users);
});
//Binding to localhost://3000
server.listen(3000, () = > {console.log('Express server started at port 3000');
});
Copy the code
We execute require(‘express’) and create a service variable using Express (). If you look closely, you’ll also see that we’ve introduced something else, which is users.js. Remember where we put the data? It is essential for the program to work.
Express has a number of ways to help us transfer specific types of content to the browser. Response.sendfile () looks for the file and sends it to the server. We use __dirname to get the root directory path where the server is running, and then we append the string index.js to the path to make sure we locate the correct file.
Response.json () sends json content to the web page. We pass it the Users array to share as an argument. The rest of the code I think you’re familiar with from previous articles.
//index.html
<html>
<head>
<meta charset="utf-8">
<title>Home page</title>
</head>
<body>
<button>Get data</button>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script type="text/javascript">
const btn = document.querySelector('button');
btn.addEventListener('click',getData);
function getData(e){
$.ajax({
url : '/users'.method : 'GET'.success : function(data){
console.log(data);
},
error: function(err){
console.log('Failed'); }}); }</script>
</body>
</html>
Copy the code
Execute Node server.js at the root of the folder, now open your browser to localhost:3000, press the button and open your browser console.
The BTN. AddEventListent (‘ click ‘, getData); In this line of code, getData makes a GET request via AJAX, using the $.ajax({properties}) function to set parameters such as URL, success, and error.
In a real production environment, you need to do more than just read JSON files. You may also want to add, delete, modify, and review data. The Express framework binds these operations to specific HTTP verbs, such as the keywords POST, GET, PUT, and DELETE.
For an in-depth look at how to write apis with Express, you can read Build a RESTful API with Express 4 by Chris Sevilleja.
Use socket for network connection
Computer networks are connections between computers that share received data. To do networking in NodeJS, we need to introduce the NET module.
const net = require('net');
Copy the code
In TCP there must be two terminals, one bound to a specified port and the other accessing the specified port.
If you’re still wondering, take a look at this example:
In the case of your mobile phone, once you buy a SIM card, you are tied to the PHONE number of the SIM. When your friends want to call you, they have to call this number. So you are one TCP terminal and your friend is another terminal.
Now do you understand?
To better absorb this knowledge, let’s write a program that listens to a file and notifies clients connected to it when the file changes.
- Create a folder named
node-network
; - Create 3 files:
filewatcher.js
,subject.txt
andclient.js
. Copy the following code intofilewatcher.js
.//filewatcher.js const net = require('net'), fs = require('fs'), filename = process.argv[2], server = net.createServer((connection)=>{ console.log('Subscriber connected'); connection.write(`watching ${filename} for changes`); let watcher = fs.watch(filename,(err,data)=>{ connection.write(`${filename} has changed`); }); connection.on('close',()=>{ console.log('Subscriber disconnected'); watcher.close(); }); }); server.listen(3000,()=>console.log('listening for subscribers')); Copy the code
- Next we provide a monitored file in
subject.txt
Write the following:Hello world, I'm gonna change Copy the code
- Then, create a new client. The following code is copied to
client.js
.const net = require('net'); let client = net.connect({port:3000}); client.on('data',(data)=>{ console.log(data.toString()); }); Copy the code
- Finally, we need two more terminals. In the first terminal we run
filename.js
, followed by the file name we want to listen for.Nodefilewatcher.js subject.txt // listen for subscribersCopy the code
On the other terminal, the client, we run client.js. Nodeclient.js Now, modify subject.txt, and then look at the client command line and notice an additional message:
//subject.txt has changed.
Copy the code
One of the main characteristics of a network is that many clients can access the network simultaneously. Open another command line window, type Node client.js to start another client, and then modify the subject.txt file. And what does it output?
What did we do?
If you don’t understand, don’t worry, let’s go over it again.
Our fileWatcher.js does three things:
net.createServer()
Create a server and send information to many clients.- Notifies the server of a client connection and tells the client that a file is being listened on.
- Finally, use Wactherbianl to listen for the file and close it when the client port connects.
Take a look at fileWatcher.js again.
//filewatcher.js
const net = require('net'),
fs = require('fs'),
filename = process.argv[2],
server = net.createServer((connection) = >{
console.log('Subscriber connected');
connection.write(`watching ${filename} for changes`);
let watcher = fs.watch(filename,(err,data)=>{
connection.write(`${filename} has changed`);
});
connection.on('close', () = > {console.log('Subscriber disconnected');
watcher.close();
});
});
server.listen(3000, () = >console.log('listening for subscribers'));
Copy the code
We introduced two modules: FS and NET to read and write files and perform network connections. Did you notice process.argv[2]? Process is a global variable that provides important information about how the NodeJS code is running. Argv [] is an array of arguments, and when we get argv[2], we want the third argument to run the code. Remember when on the command line we entered the filename as the third argument?
node filewatcher.js subject.txt
Copy the code
In addition, we saw some very familiar code, such as net.createserver (), which receives a callback function that fires when the client connects to the port. This callback takes only one object parameter to interact with the client.
The connection.write() method sends data to any client connected to port 3000. At this point, our connetion object starts working, notifying the client that a file is being listened on.
Wactcher contains a method that sends information to the client when the file is modified. And after the client disconnects, the close event is triggered, and the event handler sends a message to the server to close watcher and stop listening.
//client.js
const net = require('net'),
client = net.connect({port:3000});
client.on('data',(data)=>{
console.log(data.toString());
});
Copy the code
Client.js is simple, we import the NET module and call the connect method to access port 3000, then listen for every data event and print out the data.
Every time our fileWatcher.js executes connection.write(), our client fires a data event.
That’s just scratching the surface of how the web works. Basically, when an endpoint broadcasts information, it triggers data events on all clients connected to that endpoint.
If you want to learn more about Node networking, check out the official NodeJS documentation: the NET module. You may also want to read Building a Tcp Service Using Node.
The authors conclude
Well, that’s all I have to say. If you want to write web applications using NodeJS, you need to know more than just writing a server and routing with Express.
Here are some books I recommend: NodeJs The Right Way Web Development With Node and Express
If you have any more insights, feel free to comment below.
Translator’s note: This translation project has just started, and more and more works will be translated in the future. I’ll try to stick to it. Project address: github.com/WhiteYin/tr…