From 2e7f65b67a8b43e4b62afd07da2f6a3360c4809b Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Mon, 4 Mar 2024 16:13:44 +0100 Subject: [PATCH] =?UTF-8?q?chore(content):=20use=20=C3=A0=20100%=20new=20c?= =?UTF-8?q?odebox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/en/about/index.mdx | 21 ++- .../the-nodejs-event-emitter.md | 8 +- ...t-input-from-the-command-line-in-nodejs.md | 42 ++++- .../diagnostics/memory/using-gc-traces.md | 14 +- .../getting-started/introduction-to-nodejs.md | 23 ++- .../security-best-practices.md | 14 +- .../manipulating-files/nodejs-file-paths.md | 12 +- .../manipulating-files/nodejs-file-stats.md | 59 ++++++- .../reading-files-with-nodejs.md | 38 +++- ...working-with-file-descriptors-in-nodejs.md | 50 +++++- .../working-with-folders-in-nodejs.md | 107 +++++++++++- .../modules/anatomy-of-an-http-transaction.md | 162 +++++++++++++++++- .../modules/backpressuring-in-streams.md | 105 +++++++++++- 13 files changed, 606 insertions(+), 49 deletions(-) diff --git a/pages/en/about/index.mdx b/pages/en/about/index.mdx index 1c3ffa98c4dd0..32f611e8c0c14 100644 --- a/pages/en/about/index.mdx +++ b/pages/en/about/index.mdx @@ -20,8 +20,25 @@ scalable network applications. In the following "hello world" example, many connections can be handled concurrently. Upon each connection, the callback is fired, but if there is no work to be done, Node.js will sleep. -```js -const http = require('node:http'); +```cjs +const { createServer } = require('node:http'); + +const hostname = '127.0.0.1'; +const port = 3000; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Hello World'); +}); + +server.listen(port, hostname, () => { + console.log(`Server running at http://${hostname}:${port}/`); +}); +``` + +```mjs +import { createServer } from 'node:http'; const hostname = '127.0.0.1'; const port = 3000; diff --git a/pages/en/learn/asynchronous-work/the-nodejs-event-emitter.md b/pages/en/learn/asynchronous-work/the-nodejs-event-emitter.md index c8346e5877215..a5e9667d6855d 100644 --- a/pages/en/learn/asynchronous-work/the-nodejs-event-emitter.md +++ b/pages/en/learn/asynchronous-work/the-nodejs-event-emitter.md @@ -14,12 +14,18 @@ This module, in particular, offers the `EventEmitter` class, which we'll use to You initialize that using -```js +```cjs const EventEmitter = require('node:events'); const eventEmitter = new EventEmitter(); ``` +```mjs +import EventEmitter from 'node:events'; + +const eventEmitter = new EventEmitter(); +``` + This object exposes, among many others, the `on` and `emit` methods. - `emit` is used to trigger an event diff --git a/pages/en/learn/command-line/accept-input-from-the-command-line-in-nodejs.md b/pages/en/learn/command-line/accept-input-from-the-command-line-in-nodejs.md index 26cc7fc183b8b..e0daddbecf2f0 100644 --- a/pages/en/learn/command-line/accept-input-from-the-command-line-in-nodejs.md +++ b/pages/en/learn/command-line/accept-input-from-the-command-line-in-nodejs.md @@ -10,15 +10,31 @@ How to make a Node.js CLI program interactive? Node.js since version 7 provides the [`readline` module](https://nodejs.org/api/readline.html) to perform exactly this: get input from a readable stream such as the `process.stdin` stream, which during the execution of a Node.js program is the terminal input, one line at a time. -```js -const readline = require('node:readline').createInterface({ +```cjs +const readline = require('node:readline'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +rl.question(`What's your name?`, name => { + console.log(`Hi ${name}!`); + rl.close(); +}); +``` + +```mjs +import readline from 'node:readline'; + +const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); -readline.question(`What's your name?`, name => { +rl.question(`What's your name?`, name => { console.log(`Hi ${name}!`); - readline.close(); + rl.close(); }); ``` @@ -38,7 +54,7 @@ A more complete and abstract solution is provided by the [Inquirer.js package](h You can install it using `npm install inquirer`, and then you can replicate the above code like this: -```js +```cjs const inquirer = require('inquirer'); const questions = [ @@ -54,6 +70,22 @@ inquirer.prompt(questions).then(answers => { }); ``` +```mjs +import inquirer from 'inquirer'; + +const questions = [ + { + type: 'input', + name: 'name', + message: "What's your name?", + }, +]; + +inquirer.prompt(questions).then(answers => { + console.log(`Hi ${answers.name}!`); +}); +``` + Inquirer.js lets you do many things like asking multiple choices, having radio buttons, confirmations, and more. It's worth knowing all the alternatives, especially the built-in ones provided by Node.js, but if you plan to take CLI input to the next level, Inquirer.js is an optimal choice. diff --git a/pages/en/learn/diagnostics/memory/using-gc-traces.md b/pages/en/learn/diagnostics/memory/using-gc-traces.md index be33f6ee56ec4..e8836e803e645 100644 --- a/pages/en/learn/diagnostics/memory/using-gc-traces.md +++ b/pages/en/learn/diagnostics/memory/using-gc-traces.md @@ -23,10 +23,10 @@ and what is the outcome. For the proposal of this guide, we'll use this script: -```js +```mjs // script.mjs -import os from 'os'; +import os from 'node:os'; let len = 1_000_000; const entries = new Set(); @@ -246,10 +246,10 @@ our entries, we could use a file. Let's modify our script a bit: -```js +```mjs // script-fix.mjs -import os from 'os'; -import fs from 'fs/promises'; +import os from 'node:os'; +import fs from 'node:fs/promises'; let len = 1_000_000; const fileName = `entries-${Date.now()}`; @@ -332,8 +332,8 @@ v8.setFlagsFromString('--notrace-gc'); In Node.js, you can use [performance hooks][] to trace garbage collection. -```js -const { PerformanceObserver } = require('perf_hooks'); +```cjs +const { PerformanceObserver } = require('node:perf_hooks'); // Create a performance observer const obs = new PerformanceObserver(list => { diff --git a/pages/en/learn/getting-started/introduction-to-nodejs.md b/pages/en/learn/getting-started/introduction-to-nodejs.md index 11995db5b8b03..4256c3e883704 100644 --- a/pages/en/learn/getting-started/introduction-to-nodejs.md +++ b/pages/en/learn/getting-started/introduction-to-nodejs.md @@ -24,8 +24,25 @@ In Node.js the new ECMAScript standards can be used without problems, as you don The most common example Hello World of Node.js is a web server: -```js -const http = require('node:http'); +```cjs +const { createServer } = require('node:http'); + +const hostname = '127.0.0.1'; +const port = 3000; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Hello World'); +}); + +server.listen(port, hostname, () => { + console.log(`Server running at http://${hostname}:${port}/`); +}); +``` + +```mjs +import { createServer } from 'node:http'; const hostname = '127.0.0.1'; const port = 3000; @@ -33,7 +50,7 @@ const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); - res.end('Hello World\n'); + res.end('Hello World'); }); server.listen(port, hostname, () => { diff --git a/pages/en/learn/getting-started/security-best-practices.md b/pages/en/learn/getting-started/security-best-practices.md index 375ba50859567..1e8df8cf434b3 100644 --- a/pages/en/learn/getting-started/security-best-practices.md +++ b/pages/en/learn/getting-started/security-best-practices.md @@ -43,7 +43,7 @@ it correctly. Ensure that the WebServer handles socket errors properly, for instance, when a server is created without an error handler, it will be vulnerable to DoS -```js +```cjs const net = require('node:net'); const server = net.createServer(function (socket) { @@ -55,6 +55,18 @@ const server = net.createServer(function (socket) { server.listen(5000, '0.0.0.0'); ``` +```mjs +import net from 'node:net'; + +const server = net.createServer(function (socket) { + // socket.on('error', console.error) // this prevents the server to crash + socket.write('Echo server\r\n'); + socket.pipe(socket); +}); + +server.listen(5000, '0.0.0.0'); +``` + If a _bad request_ is performed the server could crash. An example of a DoS attack that is not caused by the request's contents is diff --git a/pages/en/learn/manipulating-files/nodejs-file-paths.md b/pages/en/learn/manipulating-files/nodejs-file-paths.md index 8ad9b49e3e1e4..ce07883a696ba 100644 --- a/pages/en/learn/manipulating-files/nodejs-file-paths.md +++ b/pages/en/learn/manipulating-files/nodejs-file-paths.md @@ -22,7 +22,7 @@ Given a path, you can extract information out of it using those methods: ### Example -```js +```cjs const path = require('node:path'); const notes = '/users/joe/notes.txt'; @@ -32,6 +32,16 @@ path.basename(notes); // notes.txt path.extname(notes); // .txt ``` +```mjs +import path from 'node:path'; + +const notes = '/users/joe/notes.txt'; + +path.dirname(notes); // /users/joe +path.basename(notes); // notes.txt +path.extname(notes); // .txt +``` + You can get the file name without the extension by specifying a second argument to `basename`: ```js diff --git a/pages/en/learn/manipulating-files/nodejs-file-stats.md b/pages/en/learn/manipulating-files/nodejs-file-stats.md index 13f3cc0a01049..38d92cc01aef1 100644 --- a/pages/en/learn/manipulating-files/nodejs-file-stats.md +++ b/pages/en/learn/manipulating-files/nodejs-file-stats.md @@ -10,7 +10,7 @@ Every file comes with a set of details that we can inspect using Node.js. In par You call it passing a file path, and once Node.js gets the file details it will call the callback function you pass, with 2 parameters: an error message, and the file stats: -```js +```cjs const fs = require('node:fs'); fs.stat('/Users/joe/test.txt', (err, stats) => { @@ -21,9 +21,20 @@ fs.stat('/Users/joe/test.txt', (err, stats) => { }); ``` +```mjs +import fs from 'node:fs/promises'; + +fs.stat('/Users/joe/test.txt', (err, stats) => { + if (err) { + console.error(err); + } + // we have access to the file stats in `stats` +}); +``` + Node.js also provides a sync method, which blocks the thread until the file stats are ready: -```js +```cjs const fs = require('node:fs'); try { @@ -33,6 +44,16 @@ try { } ``` +```mjs +import fs from 'node:fs/promises'; + +try { + const stats = fs.statSync('/Users/joe/test.txt'); +} catch (err) { + console.error(err); +} +``` + The file information is included in the stats variable. What kind of information can we extract using the stats? **A lot, including:** @@ -43,7 +64,7 @@ The file information is included in the stats variable. What kind of information There are other advanced methods, but the bulk of what you'll use in your day-to-day programming is this. -```js +```cjs const fs = require('node:fs'); fs.stat('/Users/joe/test.txt', (err, stats) => { @@ -59,9 +80,25 @@ fs.stat('/Users/joe/test.txt', (err, stats) => { }); ``` +```mjs +import fs from 'node:fs'; + +fs.stat('/Users/joe/test.txt', (err, stats) => { + if (err) { + console.error(err); + return; + } + + stats.isFile(); // true + stats.isDirectory(); // false + stats.isSymbolicLink(); // false + stats.size; // 1024000 //= 1MB +}); +``` + You can also use promise-based `fsPromises.stat()` method offered by the `fs/promises` module if you like: -```js +```cjs const fs = require('node:fs/promises'); async function example() { @@ -78,4 +115,18 @@ async function example() { example(); ``` +```mjs +import fs from 'node:fs/promises'; + +try { + const stats = await fs.stat('/Users/joe/test.txt'); + stats.isFile(); // true + stats.isDirectory(); // false + stats.isSymbolicLink(); // false + stats.size; // 1024000 //= 1MB +} catch (err) { + console.log(err); +} +``` + You can read more about the `fs` module in the [official documentation](https://nodejs.org/api/fs.html). diff --git a/pages/en/learn/manipulating-files/reading-files-with-nodejs.md b/pages/en/learn/manipulating-files/reading-files-with-nodejs.md index 55b17d98761d2..749a7ae026ae0 100644 --- a/pages/en/learn/manipulating-files/reading-files-with-nodejs.md +++ b/pages/en/learn/manipulating-files/reading-files-with-nodejs.md @@ -8,7 +8,7 @@ authors: flaviocopes, MylesBorins, fhemberger, LaRuaNa, ahmadawais, clean99 The simplest way to read a file in Node.js is to use the `fs.readFile()` method, passing it the file path, encoding and a callback function that will be called with the file data (and the error): -```js +```cjs const fs = require('node:fs'); fs.readFile('/Users/joe/test.txt', 'utf8', (err, data) => { @@ -20,9 +20,21 @@ fs.readFile('/Users/joe/test.txt', 'utf8', (err, data) => { }); ``` +```mjs +import fs from 'node:fs/promises'; + +fs.readFile('/Users/joe/test.txt', 'utf8', (err, data) => { + if (err) { + console.error(err); + return; + } + console.log(data); +}); +``` + Alternatively, you can use the synchronous version `fs.readFileSync()`: -```js +```cjs const fs = require('node:fs'); try { @@ -33,6 +45,17 @@ try { } ``` +```mjs +import fs from 'node:fs'; + +try { + const data = fs.readFileSync('/Users/joe/test.txt', 'utf8'); + console.log(data); +} catch (err) { + console.error(err); +} +``` + You can also use the promise-based `fsPromises.readFile()` method offered by the `fs/promises` module: ```js @@ -49,6 +72,17 @@ async function example() { example(); ``` +```mjs +import fs from 'node:fs/promises'; + +try { + const data = await fs.readFile('/Users/joe/test.txt', { encoding: 'utf8' }); + console.log(data); +} catch (err) { + console.log(err); +} +``` + All three of `fs.readFile()`, `fs.readFileSync()` and `fsPromises.readFile()` read the full content of the file in memory before returning the data. This means that big files are going to have a major impact on your memory consumption and speed of execution of the program. diff --git a/pages/en/learn/manipulating-files/working-with-file-descriptors-in-nodejs.md b/pages/en/learn/manipulating-files/working-with-file-descriptors-in-nodejs.md index 27cf097ca3127..afaf956306373 100644 --- a/pages/en/learn/manipulating-files/working-with-file-descriptors-in-nodejs.md +++ b/pages/en/learn/manipulating-files/working-with-file-descriptors-in-nodejs.md @@ -10,7 +10,15 @@ Before you're able to interact with a file that sits in your filesystem, you mus A file descriptor is a reference to an open file, a number (fd) returned by opening the file using the `open()` method offered by the `fs` module. This number (`fd`) uniquely identifies an open file in operating system: -```js +```cjs +const fs = require('node:fs'); + +fs.open('/Users/joe/test.txt', 'r', (err, fd) => { + // fd is our file descriptor +}); +``` + +```mjs const fs = require('node:fs'); fs.open('/Users/joe/test.txt', 'r', (err, fd) => { @@ -33,7 +41,7 @@ That flag means we open the file for reading. You can also open the file by using the `fs.openSync` method, which returns the file descriptor, instead of providing it in a callback: -```js +```cjs const fs = require('node:fs'); try { @@ -43,13 +51,23 @@ try { } ``` +```mjs +import fs from 'node:fs'; + +try { + const fd = fs.openSync('/Users/joe/test.txt', 'r'); +} catch (err) { + console.error(err); +} +``` + Once you get the file descriptor, in whatever way you choose, you can perform all the operations that require it, like calling `fs.close()` and many other operations that interact with the filesystem. You can also open the file by using the promise-based `fsPromises.open` method offered by the `fs/promises` module. The `fs/promises` module is available starting only from Node.js v14. Before v14, after v10, you can use `require('fs').promises` instead. Before v10, after v8, you can use `util.promisify` to convert `fs` methods into promise-based methods. -```js +```cjs const fs = require('node:fs/promises'); // Or const fs = require('fs').promises before v14. async function example() { @@ -65,9 +83,22 @@ async function example() { example(); ``` +```mjs +import fs from 'node:fs/promises'; +// Or const fs = require('fs').promises before v14. +let filehandle; +try { + filehandle = await fs.open('/Users/joe/test.txt', 'r'); + console.log(filehandle.fd); + console.log(await filehandle.readFile({ encoding: 'utf8' })); +} finally { + if (filehandle) await filehandle.close(); +} +``` + Here is an example of `util.promisify`: -```js +```cjs const fs = require('node:fs'); const util = require('node:util'); @@ -78,4 +109,15 @@ async function example() { example(); ``` +```mjs +import fs from 'node:fs'; +import util from 'node:util'; + +async function example() { + const open = util.promisify(fs.open); + const fd = await open('/Users/joe/test.txt', 'r'); +} +example(); +``` + To see more details about the `fs/promises` module, please check [fs/promises API](https://nodejs.org/api/fs.html#promise-example). diff --git a/pages/en/learn/manipulating-files/working-with-folders-in-nodejs.md b/pages/en/learn/manipulating-files/working-with-folders-in-nodejs.md index 7c923786fd87c..ecbf7e87df1b9 100644 --- a/pages/en/learn/manipulating-files/working-with-folders-in-nodejs.md +++ b/pages/en/learn/manipulating-files/working-with-folders-in-nodejs.md @@ -16,7 +16,7 @@ Use `fs.access()` (and its promise-based `fsPromises.access()` counterpart) to c Use `fs.mkdir()` or `fs.mkdirSync()` or `fsPromises.mkdir()` to create a new folder. -```js +```cjs const fs = require('node:fs'); const folderName = '/Users/joe/test'; @@ -30,13 +30,27 @@ try { } ``` +```mjs +import fs from 'node:fs'; + +const folderName = '/Users/joe/test'; + +try { + if (!fs.existsSync(folderName)) { + fs.mkdirSync(folderName); + } +} catch (err) { + console.error(err); +} +``` + ## Read the content of a directory Use `fs.readdir()` or `fs.readdirSync()` or `fsPromises.readdir()` to read the contents of a directory. This piece of code reads the content of a folder, both files and subfolders, and returns their relative path: -```js +```cjs const fs = require('node:fs'); const folderPath = '/Users/joe'; @@ -44,6 +58,14 @@ const folderPath = '/Users/joe'; fs.readdirSync(folderPath); ``` +```mjs +import fs from 'node:fs'; + +const folderPath = '/Users/joe'; + +fs.readdirSync(folderPath); +``` + You can get the full path: ```js @@ -54,7 +76,7 @@ fs.readdirSync(folderPath).map(fileName => { You can also filter the results to only return the files, and exclude the folders: -```js +```cjs const fs = require('node:fs'); const isFile = fileName => { @@ -68,11 +90,25 @@ fs.readdirSync(folderPath) .filter(isFile); ``` +```mjs +import fs from 'node:fs'; + +const isFile = fileName => { + return fs.lstatSync(fileName).isFile(); +}; + +fs.readdirSync(folderPath) + .map(fileName => { + return path.join(folderPath, fileName); + }) + .filter(isFile); +``` + ## Rename a folder Use `fs.rename()` or `fs.renameSync()` or `fsPromises.rename()` to rename folder. The first parameter is the current path, the second the new path: -```js +```cjs const fs = require('node:fs'); fs.rename('/Users/joe', '/Users/roger', err => { @@ -83,9 +119,20 @@ fs.rename('/Users/joe', '/Users/roger', err => { }); ``` +```mjs +import fs from 'node:fs'; + +fs.rename('/Users/joe', '/Users/roger', err => { + if (err) { + console.error(err); + } + // done +}); +``` + `fs.renameSync()` is the synchronous version: -```js +```cjs const fs = require('node:fs'); try { @@ -95,9 +142,19 @@ try { } ``` +```mjs +import fs from 'node:fs'; + +try { + fs.renameSync('/Users/joe', '/Users/roger'); +} catch (err) { + console.error(err); +} +``` + `fsPromises.rename()` is the promise-based version: -```js +```cjs const fs = require('node:fs/promises'); async function example() { @@ -110,11 +167,21 @@ async function example() { example(); ``` +```mjs +import fs from 'node:fs/promises'; + +try { + await fs.rename('/Users/joe', '/Users/roger'); +} catch (err) { + console.log(err); +} +``` + ## Remove a folder Use `fs.rmdir()` or `fs.rmdirSync()` or `fsPromises.rmdir()` to remove a folder. -```js +```cjs const fs = require('node:fs'); fs.rmdir(dir, err => { @@ -126,11 +193,23 @@ fs.rmdir(dir, err => { }); ``` +```mjs +import fs from 'node:fs'; + +fs.rmdir(dir, err => { + if (err) { + throw err; + } + + console.log(`${dir} is deleted!`); +}); +``` + To remove a folder that has contents use `fs.rm()` with the option `{ recursive: true }` to recursively remove the contents. `{ recursive: true, force: true }` makes it so that exceptions will be ignored if the folder does not exist. -```js +```cjs const fs = require('node:fs'); fs.rm(dir, { recursive: true, force: true }, err => { @@ -141,3 +220,15 @@ fs.rm(dir, { recursive: true, force: true }, err => { console.log(`${dir} is deleted!`); }); ``` + +```mjs +import fs from 'node:fs'; + +fs.rm(dir, { recursive: true, force: true }, err => { + if (err) { + throw err; + } + + console.log(`${dir} is deleted!`); +}); +``` diff --git a/pages/en/learn/modules/anatomy-of-an-http-transaction.md b/pages/en/learn/modules/anatomy-of-an-http-transaction.md index cc31bd1be7eef..795bfd1fd3591 100644 --- a/pages/en/learn/modules/anatomy-of-an-http-transaction.md +++ b/pages/en/learn/modules/anatomy-of-an-http-transaction.md @@ -17,7 +17,7 @@ the API docs for each of those. Any node web server application will at some point have to create a web server object. This is done by using [`createServer`][]. -```js +```cjs const http = require('node:http'); const server = http.createServer((request, response) => { @@ -25,6 +25,14 @@ const server = http.createServer((request, response) => { }); ``` +```mjs +import http from 'node:http'; + +const server = http.createServer((request, response) => { + // magic happens here! +}); +``` + The function that's passed in to [`createServer`][] is called once for every HTTP request that's made against that server, so it's called the request handler. In fact, the [`Server`][] object returned by [`createServer`][] is an @@ -138,7 +146,7 @@ At this point, we've covered creating a server, and grabbing the method, URL, headers and body out of requests. When we put that all together, it might look something like this: -```js +```cjs const http = require('node:http'); http @@ -161,6 +169,29 @@ http .listen(8080); // Activates this server, listening on port 8080. ``` +```mjs +import http from 'node:http'; + +http + .createServer((request, response) => { + const { headers, method, url } = request; + let body = []; + request + .on('error', err => { + console.error(err); + }) + .on('data', chunk => { + body.push(chunk); + }) + .on('end', () => { + body = Buffer.concat(body).toString(); + // At this point, we have the headers, method, url and body, and can now + // do whatever we need to in order to respond to this request. + }); + }) + .listen(8080); // Activates this server, listening on port 8080. +``` + If we run this example, we'll be able to _receive_ requests, but not _respond_ to them. In fact, if you hit this example in a web browser, your request would time out, as nothing is being sent back to the client. @@ -254,7 +285,7 @@ Building on the earlier example, we're going to make a server that sends back all of the data that was sent to us by the user. We'll format that data as JSON using `JSON.stringify`. -```js +```cjs const http = require('node:http'); http @@ -294,6 +325,46 @@ http .listen(8080); ``` +```mjs +import http from 'node:http'; + +http + .createServer((request, response) => { + const { headers, method, url } = request; + let body = []; + request + .on('error', err => { + console.error(err); + }) + .on('data', chunk => { + body.push(chunk); + }) + .on('end', () => { + body = Buffer.concat(body).toString(); + // BEGINNING OF NEW STUFF + + response.on('error', err => { + console.error(err); + }); + + response.statusCode = 200; + response.setHeader('Content-Type', 'application/json'); + // Note: the 2 lines above could be replaced with this next one: + // response.writeHead(200, {'Content-Type': 'application/json'}) + + const responseBody = { headers, method, url, body }; + + response.write(JSON.stringify(responseBody)); + response.end(); + // Note: the 2 lines above could be replaced with this next one: + // response.end(JSON.stringify(responseBody)) + + // END OF NEW STUFF + }); + }) + .listen(8080); +``` + ## Echo Server Example Let's simplify the previous example to make a simple echo server, which just @@ -301,7 +372,7 @@ sends whatever data is received in the request right back in the response. All we need to do is grab the data from the request stream and write that data to the response stream, similar to what we did previously. -```js +```cjs const http = require('node:http'); http @@ -319,6 +390,24 @@ http .listen(8080); ``` +```mjs +import http from 'node:http'; + +http + .createServer((request, response) => { + let body = []; + request + .on('data', chunk => { + body.push(chunk); + }) + .on('end', () => { + body = Buffer.concat(body).toString(); + response.end(body); + }); + }) + .listen(8080); +``` + Now let's tweak this. We want to only send an echo under the following conditions: @@ -327,7 +416,7 @@ conditions: In any other case, we want to simply respond with a 404. -```js +```cjs const http = require('node:http'); http @@ -350,6 +439,29 @@ http .listen(8080); ``` +```mjs +import http from 'node:http'; + +http + .createServer((request, response) => { + if (request.method === 'POST' && request.url === '/echo') { + let body = []; + request + .on('data', chunk => { + body.push(chunk); + }) + .on('end', () => { + body = Buffer.concat(body).toString(); + response.end(body); + }); + } else { + response.statusCode = 404; + response.end(); + } + }) + .listen(8080); +``` + > By checking the URL in this way, we're doing a form of "routing". > Other forms of routing can be as simple as `switch` statements or as complex as > whole frameworks like [`express`][]. If you're looking for something that does @@ -360,7 +472,7 @@ is a [`ReadableStream`][] and the `response` object is a [`WritableStream`][]. That means we can use [`pipe`][] to direct data from one to the other. That's exactly what we want for an echo server! -```js +```cjs const http = require('node:http'); http @@ -375,6 +487,21 @@ http .listen(8080); ``` +```mjs +import http from 'node:http'; + +http + .createServer((request, response) => { + if (request.method === 'POST' && request.url === '/echo') { + request.pipe(response); + } else { + response.statusCode = 404; + response.end(); + } + }) + .listen(8080); +``` + Yay streams! We're not quite done yet though. As mentioned multiple times in this guide, @@ -411,6 +538,29 @@ http .listen(8080); ``` +```js +import http from 'node:http'; + +http + .createServer((request, response) => { + request.on('error', err => { + console.error(err); + response.statusCode = 400; + response.end(); + }); + response.on('error', err => { + console.error(err); + }); + if (request.method === 'POST' && request.url === '/echo') { + request.pipe(response); + } else { + response.statusCode = 404; + response.end(); + } + }) + .listen(8080); +``` + We've now covered most of the basics of handling HTTP requests. At this point, you should be able to: diff --git a/pages/en/learn/modules/backpressuring-in-streams.md b/pages/en/learn/modules/backpressuring-in-streams.md index afc4c12b73f2b..b472b7e7ad311 100644 --- a/pages/en/learn/modules/backpressuring-in-streams.md +++ b/pages/en/learn/modules/backpressuring-in-streams.md @@ -36,7 +36,7 @@ pipes, sockets, and signals. In Node.js, we find a similar mechanism called part of the internal codebase utilizes that module. As a developer, you are more than encouraged to use them too! -```js +```cjs const readline = require('node:readline'); // process.stdin and process.stdout are both instances of Streams. @@ -52,6 +52,22 @@ rl.question('Why should you use streams? ', answer => { }); ``` +```mjs +import readline from 'node:readline'; + +// process.stdin and process.stdout are both instances of Streams. +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +rl.question('Why should you use streams? ', answer => { + console.log(`Maybe it's ${answer}, maybe it's because they are awesome! :)`); + + rl.close(); +}); +``` + A good example of why the backpressure mechanism implemented through streams is a great optimization can be demonstrated by comparing the internal system tools from Node.js' [`Stream`][] implementation. @@ -67,7 +83,7 @@ While that will take a few minutes to complete, in another shell we may run a script that takes Node.js' module [`zlib`][], that wraps around another compression tool, [`gzip(1)`][]. -```js +```cjs const gzip = require('node:zlib').createGzip(); const fs = require('node:fs'); @@ -77,6 +93,18 @@ const out = fs.createWriteStream('The.Matrix.1080p.mkv.gz'); inp.pipe(gzip).pipe(out); ``` +```mjs +import { createGzip } from 'node:zlib'; +import { createReadStream, createWriteStream } from 'node:fs'; + +const gzip = createGzip(); + +const inp = createReadStream('The.Matrix.1080p.mkv'); +const out = createWriteStream('The.Matrix.1080p.mkv.gz'); + +inp.pipe(gzip).pipe(out); +``` + To test the results, try opening each compressed file. The file compressed by the [`zip(1)`][] tool will notify you the file is corrupt, whereas the compression finished by [`Stream`][] will decompress without error. @@ -95,7 +123,7 @@ cleaning up and providing a callback when the pipeline is complete. Here is an example of using pipeline: -```js +```cjs const { pipeline } = require('node:stream/promises'); const fs = require('node:fs'); const zlib = require('node:zlib'); @@ -118,9 +146,32 @@ pipeline( ); ``` +```mjs +import { pipeline } from 'node:stream/promises'; +import fs from 'node:fs'; +import zlib from 'node:zlib'; + +// Use the pipeline API to easily pipe a series of streams +// together and get notified when the pipeline is fully done. +// A pipeline to gzip a potentially huge video file efficiently: + +pipeline( + fs.createReadStream('The.Matrix.1080p.mkv'), + zlib.createGzip(), + fs.createWriteStream('The.Matrix.1080p.mkv.gz'), + err => { + if (err) { + console.error('Pipeline failed', err); + } else { + console.log('Pipeline succeeded'); + } + } +); +``` + You can also call [`promisify`][] on pipeline to use it with `async` / `await`: -```js +```cjs const stream = require('node:stream'); const fs = require('node:fs'); const zlib = require('node:zlib'); @@ -142,6 +193,28 @@ async function run() { } ``` +```mjs +import stream from 'node:stream'; +import fs from 'node:fs'; +import zlib from 'node:zlib'; +import util from 'node:util'; + +const pipeline = util.promisify(stream.pipeline); + +async function run() { + try { + await pipeline( + fs.createReadStream('The.Matrix.1080p.mkv'), + zlib.createGzip(), + fs.createWriteStream('The.Matrix.1080p.mkv.gz') + ); + console.log('Pipeline succeeded'); + } catch (err) { + console.error('Pipeline failed', err); + } +} +``` + ## Too Much Data, Too Quickly There are instances where a [`Readable`][] stream might give data to the @@ -504,7 +577,7 @@ readable.on('data', data => writable.write(data)); Here's an example of using [`.push()`][] with a Readable stream. -```js +```cjs const { Readable } = require('node:stream'); // Create a custom Readable stream @@ -526,6 +599,28 @@ myReadableStream.on('data', chunk => { // { message: 'Hello, world!' } ``` +```mjs +import { Readable } from 'stream'; + +// Create a custom Readable stream +const myReadableStream = new Readable({ + objectMode: true, + read(size) { + // Push some data onto the stream + this.push({ message: 'Hello, world!' }); + this.push(null); // Mark the end of the stream + }, +}); + +// Consume the stream +myReadableStream.on('data', chunk => { + console.log(chunk); +}); + +// Output: +// { message: 'Hello, world!' } +``` + In this example, we create a custom Readable stream that pushes a single object onto the stream using [`.push()`][]. The [`._read()`][] method is called when the stream is ready to consume data, and in this case, we immediately push some data onto the stream and