- Hands-On JavaScript for Python Developers
- Sonyl Nagale
- 2838字
- 2021-06-11 17:59:50
Can We Use JavaScript Server-Side? Sure!
We don't typically think of JavaScript existing server-side, as the majority of its history has only been client-side in a browser. However, at the end of the day, JavaScript is a language—and languages can be agnostic to their application (to an extent). While it was possible to use JavaScript server-side since its beginning with a few different tools, the introduction of Node.js brought using JavaScript on the server-side into the mainstream. There are more similarities between Python and JavaScript here than on the frontend, but there are still significant differences between how each technology is used in practice. Let's take a look at Node.js and how we can leverage its power on the server-side—and why we would want to!
The following topics will be covered in this chapter:
- Why use JavaScript on the server-side?
- The Node.js ecosystem
- Threading and asynchronicity
Technical requirements
You can find the code files present in this chapter on GitHub at https://github.com/PacktPublishing/Hands-on-JavaScript-for-Python-Developers.
Why use JavaScript on the server side?
There are many server-side languages: Java, PHP, Ruby, Go, and our friend Python, just to name a few. So, why would we want to use JavaScript as a server-side language? One answer is to reduce context switching. In theory, the same developer can write both the front- and backend of a web application with a minimum of mental changes. The research behind the cost of switching programming languages is light so far and tends to be highly anecdotal, but some studies have shown that the cognitive overhead of switching from one task to another and back again reduces productivity and increases the length of time it takes to complete a task. By extension, switching from JavaScript to Python requires a few mental gymnastics. Of course, with practice, this mental overhead becomes unimportant (think of a translator who can in real time listen to one language and translate this to a different language). However, with the speed at which technology changes, reaching that level of fluency is harder. It stands to reason that the more consistency between tasks, the less the mental overhead involved in switching between the tasks.
Let's take a look at grammatical similarities between the coding languages we've discussed in terms of syntax and style, and a bit more history.
Grammatical similarities
One of the reasons that developers enjoy working with Node.js is that it's syntactically virtually identical to frontend JavaScript.
Let's take a look at some of the code we've already written.
Here is an example of JavaScript code:
document.getElementById('submit').onclick = event => {
event.preventDefault()
fetch('/data')
.then(res => res.text())
.then(response => alert(response))
.catch(err => console.error(err))
}
Now, let's take a look at some Node.js code that does something completely different, but with similar grammar, with dot notation, curly braces, and such. Here is an example of this:
const http = require('http')
http.createServer((request, response) => {
response.writeHead(200, {'Content-Type': 'text/plain'})
response.end('Hello World!')
}).listen(8080)
At first glance, these two code snippets may not look all that similar, so let's take a closer look. In our JavaScript example, take a look at event.preventDefault(), and then, in our Node.js example, the line response.end('Hello World!'). They both use dot syntax to specify a method (or function) of the parent object. These two lines are doing completely different things, but we can read them both according to the rules of JavaScript. Dot syntax is a very important concept in JavaScript, as it is inherently an object-oriented language. Much like we would when accessing class methods and properties when working with object-oriented Python, we have access to the class methods and properties of a JavaScript object. Just like in Python, we have classes, instances, methods, and properties in JavaScript.
So, what exactly is this Node.js example doing? Once again, we can see that JavaScript is a fairly legible language! Even without knowing too much about the innards of Node.js, we can see that we're creating a server, sending something, and listening for input. If we again compare to a Flask example, as follows, here's what we're doing:
from flask import Flask, Response
app = Flask(__name__)
@app.route('/')
def main():
content = {'Hello World!'}
return Response(content, status=200, mimetype='text/plain')
$ flask run --port=8080
There’s nothing inherently different about the workings of these two snippets; they are two different ways to accomplish the same goal in two different languages.
Let's take a look at a function that does the same work in client-side JavaScript and in Node.js. We haven't gone into detail on grammar quite yet, so, for the moment, don't let the syntax be a stumbling block.
Here is a JavaScript example:
for (let i = 0; i < 100; i++) {
console.log(i)
}
Here is a Node.js example:
for (let i = 0; i < 100; i++) {
console.log(i)
}
Look closely at the two. This isn’t a trick: they are, in fact, identical. Compare the JavaScript version with a basic Python loop, illustrated in the following code snippet:
for x in range(100):
print(x)
We'll get into the grammar of JavaScript and why it appears longer than its Pythonic counterpart in Chapter 3, Nitty-Gritty Grammar, but for now, let's acknowledge how different the Python code is from JavaScript.
A bit more history
Node.js, created by Ryan Dahl and originally released in 2009, is an open source runtime for JavaScript that runs outside of a browser. It may seem new, but it has gained a large foothold in its time, including major corporations. One fact that most people don't know, however, is that Node.js is not the first implementation of server-side JavaScript. That distinction again belongs to Netscape, years prior. However, many considered the language not developed enough, so its usage in this vein was limited to the point of nonexistence.
Dahl sought to bring the server side and the client side closer together. Historically, there was quite a separation of concerns between the two sides of the application. JavaScript could work with the frontend, but querying the server was a continual process. The story goes that Dahl was inspired to create Node.js when he became frustrated that file upload progress bars had to rely on constant communication with the server. Node.js presents a smoother way of performing this communication by presenting an event loop-based architecture to facilitate this communication. Since creating Node.js, Dahl has gone on to create Deno, a JavaScript and TypeScript runtime similar to Node.js. However, for our purposes, we'll be using Node.js.
We'll get into the callback paradigm used by Node.js later, and we'll also see how frontend JavaScript uses it too.
Let's take a look at how Node.js works by taking a closer look at its proverbial life cycle.
The Node.js ecosystem
Most languages aren't of the paradigm: of just writing self-contained code. Independent modules of code, called packages, are widely used in software engineering and development. To think of this in another way, even a fresh web server doesn't have software on it to serve a site out of the box. You have to install a package of software, such as Apache or nginx, to even get to the "Hello World!" step of a website. Node.js is no different. It has a number of tools to make the process of getting these packages simpler, though. Let's take a look from the ground up at a basic "Hello World!" example of a server using Node.js. We'll be discussing these concepts in more detail later, so, for now, let's just go through the basic setup.
Node.js
Of course, the first thing we need is access to the language itself. There are a few methods by which you can get Node.js on your machine, including package managers, but the most straightforward way is just to download it from the official site: https://nodejs.org. You'll also want some familiarity with your Terminal program and basic commands. Be sure to include Node Package Manager (npm) when installing. Depending on your environment, you may need to reboot your machine when the installation is complete.
Once you've installed Node.js, ensure that you have access to it. Open your Terminal and execute the following command:
$ node -v
You should see a version number returned. If so, you're ready to move on!
npm
One of the powers of Node.js is its rich open source community. Of course, this isn't in any way unique to Node.js, but it is an attractive fact. Just as there is pip for Python, there is npm for Node.js. With hundreds of thousands of packages and billions of downloads, npm is the largest package registry in the world. Of course, with packages come a web of interdependencies and the need to keep them up to date, so npm provides a reasonably stable version management method to ensure that the packages you use function together properly in concert.
Just as we tested our Node version, we'll test npm, like this:
$ npm -v
If for some reason you do not have npm installed, it's time to do some research on how to install it, since the original install of Node didn't come with npm. There are several ways to install it, such as with Homebrew, but it may be best to revisit how you installed Node.
Express.js
Express is a fast, popular web application framework. We'll be using it as the basis of our Node.js work. We'll discuss using it in more detail later, so for now, let's give ourselves a quick scaffold upon which to work. We're going to install Express and a scaffolding tool globally, as follows:
- Use the command line to install the Express generator, by running the following command: npm install -g express express-generator.
- Use the generator to create a new directory and scaffold the application, by running the following command: express --view=hbs sample && cd sample.
- Your sample directory should now contain a skeleton, like this:
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.hbs
├── index.hbs
└── layout.hbs
- Now, we'll install the dependencies of the application by running the following command: npm install.
- It'll do some work downloading the necessary packages, and then we'll be ready to start the server, by running the following command: npm start.
- Visit http://localhost:3000/ and you should see the most exciting page of all time, as shown in the following screenshot:

Congratulations! It's your first Node.js application! Let's take a look under the hood:
Open the index.js file in the routes directory, and you should see something similar to this:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
It's worth noting at this point that you may see a difference in syntax between some Node.js examples and modern JavaScript. If you notice, these lines end with semicolons, whereas our previous examples did not. We'll get into a discussion of the different versions of JavaScript later, but for now, just keep that note in mind if it surprised you.
Let's take a look at the router.get statement, illustrated in the following code block:
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
get is referring to the HTTP verb to which the program is responding. Similarly, if we were dealing with POST data, the beginning of the line would be router.post. So, in essence, this is saying: "Hey server, when you get a request for the home page, render the index template with the title variable equal to Express." Don't worry—we'll go into much more detail on this in Chapter 13, Using Express, but for now, let's play around a little:
- Add the line console.log('hello') before the res.render line.
- Change Express to My Site.
When making changes to Node.js code, you'll need to restart the local server. You can go back to your Terminal and use Ctrl + C to quit Express and then npm start to restart it. Of course, there are process managers to handle this for you, but for now, we're using a very bare-bones implementation.
Navigate to https://localhost:3000/ again. You should see the following:

Now, let's go back to your Terminal. When you hit your localhost, you also triggered a console.log() statement—a debugging print statement. You should see hello in line with the requests and responses Express provided, as illustrated in the following screenshot:

Using the console will prove invaluable to us, both on the client side and the server side. This is just a taste of what it can do! Go ahead and quit with Ctrl + C.
Threading and asynchronicity
As with traditional web architectures, it's important to understand the why of using Node.js on the backend.
We've taken a look at the how of running Node.js, so now, let's take a look at how Node's client-server architecture differs from the traditional paradigm.
Traditional client-server architecture
To understand how Node.js differs from traditional architectures, let's look at the following request diagram:

In a traditional setup, each request (or connection) to the server spawns a new thread in memory on the server, taking up system random-access memory (RAM) until the number of possible threads is reached. After that, some requests must wait until more memory is available. If you're not familiar with the concept of threads, they're basically a small sequence of commands to run on a computer. What this multithreaded paradigm implies is that for each new request received by the server, a new unique place is created in memory in order to handle the request.
Now, keep in mind that a request is not a whole web page—a page can have dozens of requests for other supplementary assets such as images. In the following screenshot, take a look at the fact that the Google home page alone has 16 requests:

Why is this important? In a nutshell: scalability. The more requests per second, the more memory is being used. We've all seen what happens when a website crashes under load—a nasty error page. This is something we all want to avoid.
Node.js architecture
In contrast to this paradigm, Node.js is single-threaded, allowing for thousands of non-blocking input-output calls without the need for additional overhead, as illustrated in the following diagram:

One thing to take note of early on, however: this paradigm isn't a silver bullet for managing traffic and load on a server. There really is no bullet-proof solution (yet) for the problem of large amounts of traffic. However, this structure does help a server to be more performant.
One of the reasons why Node.js pairs so well with JavaScript is that it's already dealing with the idea of events. As we'll see, events are a powerful cornerstone of JavaScript on the frontend, and so it stands to reason that by carrying over this process to the backend, we'll see a bit of a different approach from other architectures.
Summary
While the concept of running JavaScript on a server isn't new, its popularity, stability, and features are greatly expanded with Node.js. Early on, server-side JavaScript was abandoned but came to light again in 2009 with the creation of Node.js.
Node.js reduces the context-switching mental overhead for developers by working with the same fundamental grammar on both the client and server side. The same developer can work through the whole stack rather seamlessly because there are considerable similarities between the client-side work and how to operate with Node.js on the server. Along with a difference in approach also comes a different fundamental paradigm for handling requests to the server, compared to other more traditional implementations.
JavaScript: it's not just client-side anymore!
In the next chapter, we're going to take a deep dive into the grammar of JavaScript: syntax, semantics, and best practices.
Questions
Try your hand at answering the following questions to test your knowledge:
- True or false: Node.js is single-threaded.
- True or false: The architecture of Node.js makes it impervious to Distributed Denial of Service (DDoS) attacks.
- Who originally created Node.js?
- Brendan Eich
- Linux Torvalds
- Ada Lovelace
- Ryan Dahl
- True or false: JavaScript on the server side is inherently insecure because the code is exposed on the frontend.
- True or false: Node.js is inherently superior to Python.
Further reading
Refer to the following links to get more information on this topic:
- Why The Hell Would I Use Node.js? A Case-by-Case Tutorial: https://www.toptal.com/nodejs/why-the-hell-would-i-use-node-js
- Event-driven architecture: https://en.wikipedia.org/wiki/Event-driven_architecture
- Vue.js設計與實現
- jQuery Mobile Web Development Essentials(Third Edition)
- Python入門很簡單
- 算法訓練營:入門篇(全彩版)
- 你必須知道的204個Visual C++開發問題
- CKA/CKAD應試教程:從Docker到Kubernetes完全攻略
- Java項目實戰精編
- HTML5+CSS3網頁設計
- Python Deep Learning
- HTML5+CSS3+jQuery Mobile+Bootstrap開發APP從入門到精通(視頻教學版)
- React.js實戰
- JavaScript編程精解(原書第3版)
- 瘋狂Java講義精粹
- Mastering Android NDK
- 數字圖像處理與機器視覺:Visual C++與Matlab實現(第2版)