From cb438171482a8c3cf02b65dc8cd845632338130e Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 22 Feb 2026 00:40:35 +0000 Subject: [PATCH] feat: add remark plugin to rewrite localized links in Markdown content --- astro/astro.config.mjs | 2 + astro/src/content/docs/en/4x/guide/routing.md | 42 +- astro/src/content/docs/en/5x/guide/routing.md | 42 +- astro/src/content/docs/es/5x/guide/routing.md | 417 ++++++++++++++++++ .../utils/remark/rewrite-localized-links.mjs | 236 ++++++++++ 5 files changed, 697 insertions(+), 42 deletions(-) create mode 100755 astro/src/content/docs/es/5x/guide/routing.md create mode 100644 astro/src/utils/remark/rewrite-localized-links.mjs diff --git a/astro/astro.config.mjs b/astro/astro.config.mjs index 095044e693..1e41fd7e46 100644 --- a/astro/astro.config.mjs +++ b/astro/astro.config.mjs @@ -2,11 +2,13 @@ import { defineConfig } from 'astro/config'; import mdx from '@astrojs/mdx'; import icon from 'astro-icon'; +import remarkRewriteLocalizedLinks from './src/utils/remark/rewrite-localized-links.mjs'; // https://astro.build/config export default defineConfig({ integrations: [mdx(), icon()], markdown: { + remarkPlugins: [[remarkRewriteLocalizedLinks, { prefixes: ['guide', 'starter', 'api'] }]], shikiConfig: { theme: 'github-dark', }, diff --git a/astro/src/content/docs/en/4x/guide/routing.md b/astro/src/content/docs/en/4x/guide/routing.md index cd1f1db834..e49c4ae959 100755 --- a/astro/src/content/docs/en/4x/guide/routing.md +++ b/astro/src/content/docs/en/4x/guide/routing.md @@ -6,12 +6,12 @@ description: Learn how to define and use routes in Express.js applications, incl # Routing _Routing_ refers to how an application's endpoints (URIs) respond to client requests. -For an introduction to routing, see [Basic routing](/{{ page.lang }}/starter/basic-routing.html). +For an introduction to routing, see [Basic routing](/starter/basic-routing.html). You define routing using methods of the Express `app` object that correspond to HTTP methods; for example, `app.get()` to handle GET requests and `app.post` to handle POST requests. For a full list, -see [app.METHOD](/{{ page.lang }}/5x/api.html#app.METHOD). You can also use [app.all()](/{{ page.lang }}/5x/api.html#app.all) to handle all HTTP methods and [app.use()](/{{ page.lang }}/5x/api.html#app.use) to -specify middleware as the callback function (See [Using middleware](/{{ page.lang }}/guide/using-middleware.html) for details). +see [app.METHOD](/api/application/app-METHOD). You can also use [app.all()](/api/application/app-all) to handle all HTTP methods and [app.use()](/api/application/app-use) to +specify middleware as the callback function (See [Using middleware](/guide/using-middleware.html) for details). These routing methods specify a callback function (sometimes called "handler functions") called when the application receives a request to the specified route (endpoint) and HTTP method. In other words, the application "listens" for requests that match the specified route(s) and method(s), and when it detects a match, it calls the specified callback function. @@ -50,7 +50,7 @@ app.post('/', (req, res) => { ``` Express supports methods that correspond to all HTTP request methods: `get`, `post`, and so on. -For a full list, see [app.METHOD](/{{ page.lang }}/5x/api.html#app.METHOD). +For a full list, see [app.METHOD](/api/application/app-METHOD). There is a special routing method, `app.all()`, used to load middleware functions at a path for _all_ HTTP request methods. For example, the following handler is executed for requests to the route `"/secret"` whether using `GET`, `POST`, `PUT`, `DELETE`, or any other HTTP request method supported in the [http module](https://nodejs.org/api/http.html#http_http_methods). @@ -65,7 +65,7 @@ app.all('/secret', (req, res, next) => { Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expressions. -{% capture caution-character %} In express 5, the characters `?`, `+`, `*`, `[]`, and `()` are handled differently than in version 4, please review the [migration guide](/{{ page.lang }}/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} +{% capture caution-character %} In express 5, the characters `?`, `+`, `*`, `[]`, and `()` are handled differently than in version 4, please review the [migration guide](/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} {% include admonitions/caution.html content=caution-character %} @@ -118,7 +118,7 @@ app.get('/random.text', (req, res) => { ### Route paths based on string patterns -{% capture caution-string-patterns %} The string patterns in Express 5 no longer work. Please refer to the [migration guide](/{{ page.lang }}/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} +{% capture caution-string-patterns %} The string patterns in Express 5 no longer work. Please refer to the [migration guide](/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} {% include admonitions/caution.html content=caution-string-patterns %} @@ -209,7 +209,7 @@ req.params: { "genus": "Prunus", "species": "persica" } ``` {% capture warning-regexp %} -In express 5, Regexp characters are not supported in route paths, for more information please refer to the [migration guide](/{{ page.lang }}/guide/migrating-5.html#path-syntax).{% endcapture %} +In express 5, Regexp characters are not supported in route paths, for more information please refer to the [migration guide](/guide/migrating-5.html#path-syntax).{% endcapture %} {% include admonitions/caution.html content=warning-regexp %} @@ -239,7 +239,7 @@ In Express 4.x, the `

Route handlers

-You can provide multiple callback functions that behave like [middleware](/{{ page.lang }}/guide/using-middleware.html) to handle a request. The only exception is that these callbacks might invoke `next('route')` to bypass the remaining route callbacks. You can use this mechanism to impose pre-conditions on a route, then pass control to subsequent routes if there's no reason to proceed with the current route. +You can provide multiple callback functions that behave like [middleware](/guide/using-middleware.html) to handle a request. The only exception is that these callbacks might invoke `next('route')` to bypass the remaining route callbacks. You can use this mechanism to impose pre-conditions on a route, then pass control to subsequent routes if there's no reason to proceed with the current route. ```js app.get('/user/:id', (req, res, next) => { @@ -334,22 +334,22 @@ app.get( The methods on the response object (`res`) in the following table can send a response to the client, and terminate the request-response cycle. If none of these methods are called from a route handler, the client request will be left hanging. -| Method | Description | -| --------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| [res.download()](/{{ page.lang }}/5x/api.html#res.download) | Prompt a file to be downloaded. | -| [res.end()](/{{ page.lang }}/5x/api.html#res.end) | End the response process. | -| [res.json()](/{{ page.lang }}/5x/api.html#res.json) | Send a JSON response. | -| [res.jsonp()](/{{ page.lang }}/5x/api.html#res.jsonp) | Send a JSON response with JSONP support. | -| [res.redirect()](/{{ page.lang }}/5x/api.html#res.redirect) | Redirect a request. | -| [res.render()](/{{ page.lang }}/5x/api.html#res.render) | Render a view template. | -| [res.send()](/{{ page.lang }}/5x/api.html#res.send) | Send a response of various types. | -| [res.sendFile()](/{{ page.lang }}/5x/api.html#res.sendFile) | Send a file as an octet stream. | -| [res.sendStatus()](/{{ page.lang }}/5x/api.html#res.sendStatus) | Set the response status code and send its string representation as the response body. | +| Method | Description | +| ------------------------------------------------ | ------------------------------------------------------------------------------------- | +| [res.download()](/api/response/res-download) | Prompt a file to be downloaded. | +| [res.end()](/api/response/res-end) | End the response process. | +| [res.json()](/api/response/res-json) | Send a JSON response. | +| [res.jsonp()](/api/response/res-jsonp) | Send a JSON response with JSONP support. | +| [res.redirect()](/api/response/res-redirect) | Redirect a request. | +| [res.render()](/api/response/res-render) | Render a view template. | +| [res.send()](/api/response/res-send) | Send a response of various types. | +| [res.sendFile()](/api/response/res-sendfile) | Send a file as an octet stream. | +| [res.sendStatus()](/api/response/res-sendstatus) | Set the response status code and send its string representation as the response body. |

app.route()

You can create chainable route handlers for a route path by using `app.route()`. -Because the path is specified at a single location, creating modular routes is helpful, as is reducing redundancy and typos. For more information about routes, see: [Router() documentation](/{{ page.lang }}/5x/api.html#router). +Because the path is specified at a single location, creating modular routes is helpful, as is reducing redundancy and typos. For more information about routes, see: [Router() documentation](/api.html#router). Here is an example of chained route handlers that are defined by using `app.route()`. @@ -410,7 +410,7 @@ app.use('/birds', birds); The app will now be able to handle requests to `/birds` and `/birds/about`, as well as call the `timeLog` middleware function that is specific to the route. -But if the parent route `/birds` has path parameters, it will not be accessible by default from the sub-routes. To make it accessible, you will need to pass the `mergeParams` option to the Router constructor [reference](/{{ page.lang }}/5x/api.html#app.use). +But if the parent route `/birds` has path parameters, it will not be accessible by default from the sub-routes. To make it accessible, you will need to pass the `mergeParams` option to the Router constructor [reference](/api/application/app-use). ```js const router = express.Router({ mergeParams: true }); diff --git a/astro/src/content/docs/en/5x/guide/routing.md b/astro/src/content/docs/en/5x/guide/routing.md index cd1f1db834..a7738ef562 100755 --- a/astro/src/content/docs/en/5x/guide/routing.md +++ b/astro/src/content/docs/en/5x/guide/routing.md @@ -6,12 +6,12 @@ description: Learn how to define and use routes in Express.js applications, incl # Routing _Routing_ refers to how an application's endpoints (URIs) respond to client requests. -For an introduction to routing, see [Basic routing](/{{ page.lang }}/starter/basic-routing.html). +For an introduction to routing, see [Basic routing](/starter/basic-routing.html). You define routing using methods of the Express `app` object that correspond to HTTP methods; for example, `app.get()` to handle GET requests and `app.post` to handle POST requests. For a full list, -see [app.METHOD](/{{ page.lang }}/5x/api.html#app.METHOD). You can also use [app.all()](/{{ page.lang }}/5x/api.html#app.all) to handle all HTTP methods and [app.use()](/{{ page.lang }}/5x/api.html#app.use) to -specify middleware as the callback function (See [Using middleware](/{{ page.lang }}/guide/using-middleware.html) for details). +see [app.METHOD](/api/application/app-METHOD). You can also use [app.all()](/api/application/app-all) to handle all HTTP methods and [app.use()](/api/application/app-use) to +specify middleware as the callback function (See [Using middleware](/guide/using-middleware.html) for details). These routing methods specify a callback function (sometimes called "handler functions") called when the application receives a request to the specified route (endpoint) and HTTP method. In other words, the application "listens" for requests that match the specified route(s) and method(s), and when it detects a match, it calls the specified callback function. @@ -50,7 +50,7 @@ app.post('/', (req, res) => { ``` Express supports methods that correspond to all HTTP request methods: `get`, `post`, and so on. -For a full list, see [app.METHOD](/{{ page.lang }}/5x/api.html#app.METHOD). +For a full list, see [app.METHOD](/api/application/app-METHOD). There is a special routing method, `app.all()`, used to load middleware functions at a path for _all_ HTTP request methods. For example, the following handler is executed for requests to the route `"/secret"` whether using `GET`, `POST`, `PUT`, `DELETE`, or any other HTTP request method supported in the [http module](https://nodejs.org/api/http.html#http_http_methods). @@ -65,7 +65,7 @@ app.all('/secret', (req, res, next) => { Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expressions. -{% capture caution-character %} In express 5, the characters `?`, `+`, `*`, `[]`, and `()` are handled differently than in version 4, please review the [migration guide](/{{ page.lang }}/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} +{% capture caution-character %} In express 5, the characters `?`, `+`, `*`, `[]`, and `()` are handled differently than in version 4, please review the [migration guide](/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} {% include admonitions/caution.html content=caution-character %} @@ -118,7 +118,7 @@ app.get('/random.text', (req, res) => { ### Route paths based on string patterns -{% capture caution-string-patterns %} The string patterns in Express 5 no longer work. Please refer to the [migration guide](/{{ page.lang }}/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} +{% capture caution-string-patterns %} The string patterns in Express 5 no longer work. Please refer to the [migration guide](/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} {% include admonitions/caution.html content=caution-string-patterns %} @@ -209,7 +209,7 @@ req.params: { "genus": "Prunus", "species": "persica" } ``` {% capture warning-regexp %} -In express 5, Regexp characters are not supported in route paths, for more information please refer to the [migration guide](/{{ page.lang }}/guide/migrating-5.html#path-syntax).{% endcapture %} +In express 5, Regexp characters are not supported in route paths, for more information please refer to the [migration guide](/guide/migrating-5.html#path-syntax).{% endcapture %} {% include admonitions/caution.html content=warning-regexp %} @@ -239,7 +239,7 @@ In Express 4.x,
the `

Route handlers

-You can provide multiple callback functions that behave like [middleware](/{{ page.lang }}/guide/using-middleware.html) to handle a request. The only exception is that these callbacks might invoke `next('route')` to bypass the remaining route callbacks. You can use this mechanism to impose pre-conditions on a route, then pass control to subsequent routes if there's no reason to proceed with the current route. +You can provide multiple callback functions that behave like [middleware](/guide/using-middleware.html) to handle a request. The only exception is that these callbacks might invoke `next('route')` to bypass the remaining route callbacks. You can use this mechanism to impose pre-conditions on a route, then pass control to subsequent routes if there's no reason to proceed with the current route. ```js app.get('/user/:id', (req, res, next) => { @@ -334,22 +334,22 @@ app.get( The methods on the response object (`res`) in the following table can send a response to the client, and terminate the request-response cycle. If none of these methods are called from a route handler, the client request will be left hanging. -| Method | Description | -| --------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| [res.download()](/{{ page.lang }}/5x/api.html#res.download) | Prompt a file to be downloaded. | -| [res.end()](/{{ page.lang }}/5x/api.html#res.end) | End the response process. | -| [res.json()](/{{ page.lang }}/5x/api.html#res.json) | Send a JSON response. | -| [res.jsonp()](/{{ page.lang }}/5x/api.html#res.jsonp) | Send a JSON response with JSONP support. | -| [res.redirect()](/{{ page.lang }}/5x/api.html#res.redirect) | Redirect a request. | -| [res.render()](/{{ page.lang }}/5x/api.html#res.render) | Render a view template. | -| [res.send()](/{{ page.lang }}/5x/api.html#res.send) | Send a response of various types. | -| [res.sendFile()](/{{ page.lang }}/5x/api.html#res.sendFile) | Send a file as an octet stream. | -| [res.sendStatus()](/{{ page.lang }}/5x/api.html#res.sendStatus) | Set the response status code and send its string representation as the response body. | +| Method | Description | +| ------------------------------------------------ | ------------------------------------------------------------------------------------- | +| [res.download()](/api/response/res-download) | Prompt a file to be downloaded. | +| [res.end()](/api/response/res-end) | End the response process. | +| [res.json()](/api/response/res-json) | Send a JSON response. | +| [res.jsonp()](/api/response/res-jsonp) | Send a JSON response with JSONP support. | +| [res.redirect()](/api/response/res-redirect) | Redirect a request. | +| [res.render()](/api/response/res-render) | Render a view template. | +| [res.send()](/api/response/res-send) | Send a response of various types. | +| [res.sendFile()](/api/response/res-sendFile) | Send a file as an octet stream. | +| [res.sendStatus()](/api/response/res-sendStatus) | Set the response status code and send its string representation as the response body. |

app.route()

You can create chainable route handlers for a route path by using `app.route()`. -Because the path is specified at a single location, creating modular routes is helpful, as is reducing redundancy and typos. For more information about routes, see: [Router() documentation](/{{ page.lang }}/5x/api.html#router). +Because the path is specified at a single location, creating modular routes is helpful, as is reducing redundancy and typos. For more information about routes, see: [Router() documentation](/api.html#router). Here is an example of chained route handlers that are defined by using `app.route()`. @@ -410,7 +410,7 @@ app.use('/birds', birds); The app will now be able to handle requests to `/birds` and `/birds/about`, as well as call the `timeLog` middleware function that is specific to the route. -But if the parent route `/birds` has path parameters, it will not be accessible by default from the sub-routes. To make it accessible, you will need to pass the `mergeParams` option to the Router constructor [reference](/{{ page.lang }}/5x/api.html#app.use). +But if the parent route `/birds` has path parameters, it will not be accessible by default from the sub-routes. To make it accessible, you will need to pass the `mergeParams` option to the Router constructor [reference](/api/application/app-use). ```js const router = express.Router({ mergeParams: true }); diff --git a/astro/src/content/docs/es/5x/guide/routing.md b/astro/src/content/docs/es/5x/guide/routing.md new file mode 100755 index 0000000000..a7738ef562 --- /dev/null +++ b/astro/src/content/docs/es/5x/guide/routing.md @@ -0,0 +1,417 @@ +--- +title: Express routing +description: Learn how to define and use routes in Express.js applications, including route methods, route paths, parameters, and using Router for modular routing. +--- + +# Routing + +_Routing_ refers to how an application's endpoints (URIs) respond to client requests. +For an introduction to routing, see [Basic routing](/starter/basic-routing.html). + +You define routing using methods of the Express `app` object that correspond to HTTP methods; +for example, `app.get()` to handle GET requests and `app.post` to handle POST requests. For a full list, +see [app.METHOD](/api/application/app-METHOD). You can also use [app.all()](/api/application/app-all) to handle all HTTP methods and [app.use()](/api/application/app-use) to +specify middleware as the callback function (See [Using middleware](/guide/using-middleware.html) for details). + +These routing methods specify a callback function (sometimes called "handler functions") called when the application receives a request to the specified route (endpoint) and HTTP method. In other words, the application "listens" for requests that match the specified route(s) and method(s), and when it detects a match, it calls the specified callback function. + +In fact, the routing methods can have more than one callback function as arguments. +With multiple callback functions, it is important to provide `next` as an argument to the callback function and then call `next()` within the body of the function to hand off control +to the next callback. + +The following code is an example of a very basic route. + +```js +const express = require('express'); +const app = express(); + +// respond with "hello world" when a GET request is made to the homepage +app.get('/', (req, res) => { + res.send('hello world'); +}); +``` + +

Route methods

+ +A route method is derived from one of the HTTP methods, and is attached to an instance of the `express` class. + +The following code is an example of routes that are defined for the `GET` and the `POST` methods to the root of the app. + +```js +// GET method route +app.get('/', (req, res) => { + res.send('GET request to the homepage'); +}); + +// POST method route +app.post('/', (req, res) => { + res.send('POST request to the homepage'); +}); +``` + +Express supports methods that correspond to all HTTP request methods: `get`, `post`, and so on. +For a full list, see [app.METHOD](/api/application/app-METHOD). + +There is a special routing method, `app.all()`, used to load middleware functions at a path for _all_ HTTP request methods. For example, the following handler is executed for requests to the route `"/secret"` whether using `GET`, `POST`, `PUT`, `DELETE`, or any other HTTP request method supported in the [http module](https://nodejs.org/api/http.html#http_http_methods). + +```js +app.all('/secret', (req, res, next) => { + console.log('Accessing the secret section ...'); + next(); // pass control to the next handler +}); +``` + +

Route paths

+ +Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expressions. + +{% capture caution-character %} In express 5, the characters `?`, `+`, `*`, `[]`, and `()` are handled differently than in version 4, please review the [migration guide](/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} + +{% include admonitions/caution.html content=caution-character %} + +{% capture note-dollar-character %}In express 4, regular expression characters such as `$` need to be escaped with a `\`. +{% endcapture %} + +{% include admonitions/caution.html content=note-dollar-character %} + +{% capture note-path-to-regexp %} + +Express uses [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) for matching the route paths; see the path-to-regexp documentation for all the possibilities in defining route paths. [Express Playground Router](https://bjohansebas.github.io/playground-router/) is a handy tool for testing basic Express routes, although it does not support pattern matching. + +{% endcapture %} + +{% include admonitions/note.html content=note-path-to-regexp %} + +{% capture query-string-note %} + +Query strings are not part of the route path. + +{% endcapture %} + +{% include admonitions/warning.html content=query-string-note %} + +### Route paths based on strings + +This route path will match requests to the root route, `/`. + +```js +app.get('/', (req, res) => { + res.send('root'); +}); +``` + +This route path will match requests to `/about`. + +```js +app.get('/about', (req, res) => { + res.send('about'); +}); +``` + +This route path will match requests to `/random.text`. + +```js +app.get('/random.text', (req, res) => { + res.send('random.text'); +}); +``` + +### Route paths based on string patterns + +{% capture caution-string-patterns %} The string patterns in Express 5 no longer work. Please refer to the [migration guide](/guide/migrating-5.html#path-syntax) for more information.{% endcapture %} + +{% include admonitions/caution.html content=caution-string-patterns %} + +This route path will match `acd` and `abcd`. + +```js +app.get('/ab?cd', (req, res) => { + res.send('ab?cd'); +}); +``` + +This route path will match `abcd`, `abbcd`, `abbbcd`, and so on. + +```js +app.get('/ab+cd', (req, res) => { + res.send('ab+cd'); +}); +``` + +This route path will match `abcd`, `abxcd`, `abRANDOMcd`, `ab123cd`, and so on. + +```js +app.get('/ab*cd', (req, res) => { + res.send('ab*cd'); +}); +``` + +This route path will match `/abe` and `/abcde`. + +```js +app.get('/ab(cd)?e', (req, res) => { + res.send('ab(cd)?e'); +}); +``` + +### Route paths based on regular expressions + +This route path will match anything with an "a" in it. + +```js +app.get(/a/, (req, res) => { + res.send('/a/'); +}); +``` + +This route path will match `butterfly` and `dragonfly`, but not `butterflyman`, `dragonflyman`, and so on. + +```js +app.get(/.*fly$/, (req, res) => { + res.send('/.*fly$/'); +}); +``` + +

Route parameters

+ +Route parameters are named URL segments that are used to capture the values specified at their position in the URL. The captured values are populated in the `req.params` object, with the name of the route parameter specified in the path as their respective keys. + +``` +Route path: /users/:userId/books/:bookId +Request URL: http://localhost:3000/users/34/books/8989 +req.params: { "userId": "34", "bookId": "8989" } +``` + +To define routes with route parameters, simply specify the route parameters in the path of the route as shown below. + +```js +app.get('/users/:userId/books/:bookId', (req, res) => { + res.send(req.params); +}); +``` + +
+The name of route parameters must be made up of "word characters" ([A-Za-z0-9_]). +
+ +Since the hyphen (`-`) and the dot (`.`) are interpreted literally, they can be used along with route parameters for useful purposes. + +``` +Route path: /flights/:from-:to +Request URL: http://localhost:3000/flights/LAX-SFO +req.params: { "from": "LAX", "to": "SFO" } +``` + +``` +Route path: /plantae/:genus.:species +Request URL: http://localhost:3000/plantae/Prunus.persica +req.params: { "genus": "Prunus", "species": "persica" } +``` + +{% capture warning-regexp %} +In express 5, Regexp characters are not supported in route paths, for more information please refer to the [migration guide](/guide/migrating-5.html#path-syntax).{% endcapture %} + +{% include admonitions/caution.html content=warning-regexp %} + +To have more control over the exact string that can be matched by a route parameter, you can append a regular expression in parentheses (`()`): + +``` +Route path: /user/:userId(\d+) +Request URL: http://localhost:3000/user/42 +req.params: {"userId": "42"} +``` + +{% capture escape-advisory %} + +Because the regular expression is usually part of a literal string, be sure to escape any `\` characters with an additional backslash, for example `\\d+`. + +{% endcapture %} + +{% include admonitions/warning.html content=escape-advisory %} + +{% capture warning-version %} + +In Express 4.x,
the `*` character in regular expressions is not interpreted in the usual way. As a workaround, use `{0,}` instead of `*`. This will likely be fixed in Express 5. + +{% endcapture %} + +{% include admonitions/warning.html content=warning-version %} + +

Route handlers

+ +You can provide multiple callback functions that behave like [middleware](/guide/using-middleware.html) to handle a request. The only exception is that these callbacks might invoke `next('route')` to bypass the remaining route callbacks. You can use this mechanism to impose pre-conditions on a route, then pass control to subsequent routes if there's no reason to proceed with the current route. + +```js +app.get('/user/:id', (req, res, next) => { + if (req.params.id === '0') { + return next('route'); + } + res.send(`User ${req.params.id}`); +}); + +app.get('/user/:id', (req, res) => { + res.send('Special handler for user ID 0'); +}); +``` + +In this example: + +- `GET /user/5` → handled by first route → sends "User 5" +- `GET /user/0` → first route calls `next('route')`, skipping to the next matching `/user/:id` route + +Route handlers can be in the form of a function, an array of functions, or combinations of both, as shown in the following examples. + +A single callback function can handle a route. For example: + +```js +app.get('/example/a', (req, res) => { + res.send('Hello from A!'); +}); +``` + +More than one callback function can handle a route (make sure you specify the `next` object). For example: + +```js +app.get( + '/example/b', + (req, res, next) => { + console.log('the response will be sent by the next function ...'); + next(); + }, + (req, res) => { + res.send('Hello from B!'); + } +); +``` + +An array of callback functions can handle a route. For example: + +```js +const cb0 = function (req, res, next) { + console.log('CB0'); + next(); +}; + +const cb1 = function (req, res, next) { + console.log('CB1'); + next(); +}; + +const cb2 = function (req, res) { + res.send('Hello from C!'); +}; + +app.get('/example/c', [cb0, cb1, cb2]); +``` + +A combination of independent functions and arrays of functions can handle a route. For example: + +```js +const cb0 = function (req, res, next) { + console.log('CB0'); + next(); +}; + +const cb1 = function (req, res, next) { + console.log('CB1'); + next(); +}; + +app.get( + '/example/d', + [cb0, cb1], + (req, res, next) => { + console.log('the response will be sent by the next function ...'); + next(); + }, + (req, res) => { + res.send('Hello from D!'); + } +); +``` + +

Response methods

+ +The methods on the response object (`res`) in the following table can send a response to the client, and terminate the request-response cycle. If none of these methods are called from a route handler, the client request will be left hanging. + +| Method | Description | +| ------------------------------------------------ | ------------------------------------------------------------------------------------- | +| [res.download()](/api/response/res-download) | Prompt a file to be downloaded. | +| [res.end()](/api/response/res-end) | End the response process. | +| [res.json()](/api/response/res-json) | Send a JSON response. | +| [res.jsonp()](/api/response/res-jsonp) | Send a JSON response with JSONP support. | +| [res.redirect()](/api/response/res-redirect) | Redirect a request. | +| [res.render()](/api/response/res-render) | Render a view template. | +| [res.send()](/api/response/res-send) | Send a response of various types. | +| [res.sendFile()](/api/response/res-sendFile) | Send a file as an octet stream. | +| [res.sendStatus()](/api/response/res-sendStatus) | Set the response status code and send its string representation as the response body. | + +

app.route()

+ +You can create chainable route handlers for a route path by using `app.route()`. +Because the path is specified at a single location, creating modular routes is helpful, as is reducing redundancy and typos. For more information about routes, see: [Router() documentation](/api.html#router). + +Here is an example of chained route handlers that are defined by using `app.route()`. + +```js +app + .route('/book') + .get((req, res) => { + res.send('Get a random book'); + }) + .post((req, res) => { + res.send('Add a book'); + }) + .put((req, res) => { + res.send('Update the book'); + }); +``` + +

express.Router

+ +Use the `express.Router` class to create modular, mountable route handlers. A `Router` instance is a complete middleware and routing system; for this reason, it is often referred to as a "mini-app". + +The following example creates a router as a module, loads a middleware function in it, defines some routes, and mounts the router module on a path in the main app. + +Create a router file named `birds.js` in the app directory, with the following content: + +```js +const express = require('express'); +const router = express.Router(); + +// middleware that is specific to this router +const timeLog = (req, res, next) => { + console.log('Time: ', Date.now()); + next(); +}; +router.use(timeLog); + +// define the home page route +router.get('/', (req, res) => { + res.send('Birds home page'); +}); +// define the about route +router.get('/about', (req, res) => { + res.send('About birds'); +}); + +module.exports = router; +``` + +Then, load the router module in the app: + +```js +const birds = require('./birds'); + +// ... + +app.use('/birds', birds); +``` + +The app will now be able to handle requests to `/birds` and `/birds/about`, as well as call the `timeLog` middleware function that is specific to the route. + +But if the parent route `/birds` has path parameters, it will not be accessible by default from the sub-routes. To make it accessible, you will need to pass the `mergeParams` option to the Router constructor [reference](/api/application/app-use). + +```js +const router = express.Router({ mergeParams: true }); +``` diff --git a/astro/src/utils/remark/rewrite-localized-links.mjs b/astro/src/utils/remark/rewrite-localized-links.mjs new file mode 100644 index 0000000000..45feb851ab --- /dev/null +++ b/astro/src/utils/remark/rewrite-localized-links.mjs @@ -0,0 +1,236 @@ +/** + * Remark plugin that rewrites selected internal links in Markdown/MDX content. + * + * Behavior summary: + * - Rewrites only absolute internal paths that match configured prefixes + * (e.g. `/guide/...`, `/starter/...`, `/api/...`). + * - Adds language segment from file path context. + * - Adds docs version segment for configured collections (by default: `docs`). + * - Preserves query string and hash fragments. + * - Skips already-localized paths (e.g. `/en/...`). + */ + +/** + * @typedef {Object} RewriteContext + * @property {'docs'|'resources'|undefined} collection Collection inferred from source path. + * @property {string} lang Language inferred from source path or fallback. + * @property {string|undefined} version Version inferred from source path when applicable. + */ + +/** + * @typedef {Object} RemarkRewriteLocalizedLinksOptions + * @property {string[]} [prefixes=['guide']] Root path segments that should be rewritten. + * @property {string} [defaultLang='en'] Language fallback when source path is not parseable. + * @property {string} [defaultVersion='5x'] Version fallback for docs pages without explicit version segment. + * @property {string[]} [versionedCollections=['docs']] Collections that should include version in rewritten links. + */ + +/** + * Validates whether a path segment matches version format (e.g. `5x`, `4x`). + * @param {string} value + */ +function isVersionSegment(value) { + return /^\d+x$/.test(value); +} + +/** + * Splits URL into pathname and suffix (`?query#hash`). + * @param {string} url + */ +function splitPathAndSuffix(url) { + const match = url.match(/^([^?#]*)(.*)$/); + if (!match) { + return { pathname: url, suffix: '' }; + } + + return { + pathname: match[1], + suffix: match[2] || '', + }; +} + +/** + * Checks whether pathname already starts with language code (e.g. `/en/...`, `/pt-br/...`). + * @param {string} pathname + */ +function hasLanguagePrefix(pathname) { + return /^\/[a-z]{2}(?:-[a-z]{2})?(?:\/|$)/i.test(pathname); +} + +/** + * Adds trailing slash for non-file paths. + * @param {string} pathname + */ +function ensureTrailingSlash(pathname) { + if (pathname.endsWith('/')) { + return pathname; + } + + if (/\.[a-z\d]+$/i.test(pathname)) { + return pathname; + } + + return `${pathname}/`; +} + +/** + * Derives collection/lang/version from the Markdown source file path. + * + * Expected path examples: + * - `/.../src/content/docs/en/5x/guide/routing.md` + * - `/.../src/content/resources/en/contributing.md` + * + * @param {string|undefined} filePath + * @param {string} defaultLang + * @param {string} defaultVersion + * @returns {RewriteContext} + */ +function deriveContextFromFile(filePath, defaultLang, defaultVersion) { + const normalizedPath = (filePath || '').replaceAll('\\', '/'); + + const docsMarker = '/src/content/docs/'; + const resourcesMarker = '/src/content/resources/'; + + if (normalizedPath.includes(docsMarker)) { + const relativePath = normalizedPath.split(docsMarker)[1] || ''; + const segments = relativePath.split('/').filter(Boolean); + + const lang = segments[0] || defaultLang; + const version = isVersionSegment(segments[1]) ? segments[1] : defaultVersion; + + return { + collection: 'docs', + lang, + version, + }; + } + + if (normalizedPath.includes(resourcesMarker)) { + const relativePath = normalizedPath.split(resourcesMarker)[1] || ''; + const segments = relativePath.split('/').filter(Boolean); + + return { + collection: 'resources', + lang: segments[0] || defaultLang, + version: undefined, + }; + } + + return { + collection: undefined, + lang: defaultLang, + version: defaultVersion, + }; +} + +/** + * Determines whether a pathname should be rewritten according to configured prefixes. + * @param {string} pathname + * @param {Set} prefixesSet + */ +function shouldRewritePath(pathname, prefixesSet) { + if (!pathname.startsWith('/')) { + return false; + } + + const normalized = pathname.replace(/^\/+/, '').replace(/\/+$/, ''); + const [firstSegment] = normalized.split('/'); + + return prefixesSet.has(firstSegment); +} + +/** + * Builds the final localized/versioned pathname. + * @param {string} pathname + * @param {RewriteContext} context + * @param {Set} versionedCollections + */ +function rewritePath(pathname, context, versionedCollections) { + const cleanTarget = pathname.replace(/^\/+/, '').replace(/\/+$/, ''); + const segments = [context.lang]; + + if (context.collection && versionedCollections.has(context.collection) && context.version) { + segments.push(context.version); + } + + segments.push(cleanTarget); + + const rewritten = `/${segments.join('/')}`.replace(/\/+/g, '/'); + return ensureTrailingSlash(rewritten); +} + +/** + * Rewrites a URL when it matches plugin rules, otherwise returns original value. + * @param {string} url + * @param {RewriteContext} context + * @param {Set} prefixesSet + * @param {Set} versionedCollections + */ +function rewriteUrl(url, context, prefixesSet, versionedCollections) { + if (typeof url !== 'string' || !url) { + return url; + } + + const { pathname, suffix } = splitPathAndSuffix(url); + + if (!pathname || pathname === '/' || pathname.startsWith('#')) { + return url; + } + + if (hasLanguagePrefix(pathname)) { + return url; + } + + if (!shouldRewritePath(pathname, prefixesSet)) { + return url; + } + + const rewrittenPath = rewritePath(pathname, context, versionedCollections); + return `${rewrittenPath}${suffix}`; +} + +/** + * Generic depth-first AST traversal utility. + * @param {any} node + * @param {(node: any) => void} visitor + */ +function walkTree(node, visitor) { + if (!node || typeof node !== 'object') { + return; + } + + visitor(node); + + if (Array.isArray(node.children)) { + for (const child of node.children) { + walkTree(child, visitor); + } + } +} + +/** + * Remark plugin factory. + * + * @param {RemarkRewriteLocalizedLinksOptions} [options] + */ +export default function remarkRewriteLocalizedLinks(options = {}) { + const { + prefixes = ['guide'], + defaultLang = 'en', + defaultVersion = '5x', + versionedCollections = ['docs'], + } = options; + + const prefixesSet = new Set(prefixes); + const versionedCollectionsSet = new Set(versionedCollections); + + return (tree, file) => { + const context = deriveContextFromFile(file?.path, defaultLang, defaultVersion); + + walkTree(tree, (node) => { + if (node.type === 'link' || node.type === 'definition') { + node.url = rewriteUrl(node.url, context, prefixesSet, versionedCollectionsSet); + } + }); + }; +}