Astro JS Middleware Example: Request Logging
Tags: astro, javascript, webdev
Published: 2023-05-31 09:34:52

---
title: "Astro JS Middleware Example: Request Logging"
published: "true"
description: "Astro JS Middleware Example 📝 how you can intercept HTTP requests for logging, modifying content and from your  🚀 Astro JS project code"
tags: "astro, javascript, webdev"
canonical_url: "https://rodneylab.com/astro-js-middleware-example/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnhlgjo86xd0z6svqx1g.png"
---

## 🚀 Astro JS Middleware Example

Astro now includes middleware, which performs a similar function to **SvelteKit hooks**, **Deno Fresh middleware** or middleware offered by serverless hosts (such as **Cloudflare Workers**).  Here, we see an Astro JS Middleware example, as well as what middleware is and when you will pull it out of the Astro tool belt.  For our example, we add request logging using Astro middleware with the **Logtail serverless logging** service.

At the time of writing, the functionality is still experimental, and the APIs may change. For that reason, check the current status before using Astro middleware in production projects.  Also, bear in mind the APIs might evolve, so check the latest docs if you encounter issues (link further down the page).

### What is Middleware?

Middleware is just code which runs on a server to intercept requests, potentially modifying them.  Astro route files will be invoked, typically for specific routes (`/docs/getting-started`, for example).  Middleware is a little different in that respect, in that it can be **less specific**; you can invoke a middleware function for **all routes**, or selected sub-paths.  With the logging example we consider, there will be some request details you want to capture, no matter which route the request was made to.  Included here might be the request pathname, the time of the request, and perhaps even the time taken to respond.  A single middleware file for all routes, rather than including the logging logic in the `.astro` file for every route, makes sense here.

We will see within your middleware function, you can peek at what the response will be, then send that response, &ldquo;as-is&rdquo; or even change certain parts, **adding HTTP headers** or even **changing out parts of the response body**, where that makes sense.  We also see Astro lets you pass through data to `.astro` files using the **locals object, offering another way to update content**.  Although **Astro middleware works on both SSR and static** routes, you will get the most out of it on SSR sites.

## 🤔 Why use Middleware?

Some uses for middleware are:

- **analytics** code;
- **logging**;
- **performance** and reliability measurement;
- manipulating HTML content (e.g. **highlighting search terms** in body text);
- adding response headers (such as **HTTP security headers**); and
- **proxying requests**, for example to <a aria-label="keep a Word Press back end URL private" href="http://plus.rodneylab.com/tutorials/get-started-sveltekit-headless-wordpress">keep a WordPress backend URL private</a>.


## 🧱 What are we Building?

We will look at code for a basic Astro JS middleware logging example.  In the code we shall see how you add a middleware function in Astro, as well as the most important methods, for intercepting requests.  We also see how you can leave the response intact, yet still pass data through, for use in your front end code.

![Astro JS Middleware Example: Screen capture shows an event logged in the Log tail console. Message reads astro-middleware-log. Timestamp and pathname are included, as, well as the event level, which is set to info.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hy7a0iq8j7syz7dz0ke8.png)

If that sounds like what you were looking for, then why don&rsquo;t we get started? We won&rsquo;t build an app from scratch, so, you probably want to create a feature branch on an existing project.  If this is your first time working in Astro, though, see the <a href="https://rodneylab.com/how-to-create-new-astro-js-app/"> quick guide on how to spin up a new Astro project.

## 🛠️ Enabling Astro Middleware

Because middleware is an experimental feature, at the time of writing, you need to enable it manually by updating `astro.config.mjs`:

```javascript
import { defineConfig } from 'astro/config';

import cloudflare from "@astrojs/cloudflare";

// https://astro.build/config
export default defineConfig({
  experimental: {
    middleware: true
  },
  output: "server",
  adapter: cloudflare()
});
```

Middleware works on static as well as SSR sites, however you get most benefit on an SSR site, so we set the `output` and `adapter` fields in lines (`10` & `11`), above.  Drop these if you want to stay static.  And, of course, switch out the Cloudflare adapter if you prefer using another host.


## 🕵🏽 Astro JS Middleware: Middleware Code

To add Astro middleware to all routes, just create a `src/middleware.ts` (or `src/middleware.js`) file which exports an `onRequest` handler:

```typescript
import type { MiddlewareHandler } from 'astro';

export const onRequest: MiddlewareHandler<Response> = async function onRequest(
	{ locals, request },
	next,
) {
	const { url } = request;
	const { pathname } = new URL(url);
	const timestamp = new Date().toISOString();

	(locals as { timestamp: string }).timestamp = timestamp;

	const response = await next();

	return response;
};
```

Some interesting points here:

- `onRequest` has two arguments: a context object and `next`
    - `request` on the context object is the incoming HTTP request from which you can destructure a URL, as well as <a href="https://docs.astro.build/en/guides/middleware/#context">other fields, which you will be familiar with</a>
    - `locals` (on the context object) lets us pass out data from middleware
    - `next` is a function; calling that function gives you access to the response that the route normally generates (without middleware)
- `onRequest` returns a response object; in this case it is just the response which would have been sent anyway, though you can modify the response, adding HTTP headers or tinkering with the body

We also set `locals.timestamp`, above.  We will see that value is passed through to the front end code a little later.

### Astro JS Middleware Example: Adding Logging

For our example, we want to log the request to our serverless logging service.  Since we are just reporting, `onRequest` can just return the response which would have been sent anyway (no need to modify the response).

Here is the extra code for logging a few response details, using Logtail:

```typescript
import type { MiddlewareHandler } from 'astro';
import { pack } from 'msgpackr';

const logtailSourceToken = import.meta.env.LOGTAIL_SOURCE_TOKEN;

export const onRequest: MiddlewareHandler<Response> = async function onRequest(
	{ locals, request },
	next,
) {
	const { url } = request;
	const { pathname } = new URL(url);
	const timestamp = new Date().toISOString();

	(locals as { timestamp: string }).timestamp = timestamp;

	const logData = {
		dt: timestamp,
		level: 'info',
		message: 'astro-middleware-log',
		item: {
			pathname,
		},
	};

	await fetch('https://in.logtail.com', {
		method: 'POST',
		headers: {
			Authorization: `Bearer ${logtailSourceToken}`,
			'Content-Type': 'application/msgpack',
		},
		body: pack(logData),
	});

	const response = await next();

	return response;
};
```

<a aria-label="Log tail has a REST A P I" href="https://betterstack.com/docs/logs/http-rest-api/">Logtail has a REST API</a>, and we can send the log data using `fetch`; notice fetch is simply available in line `25`; no need to import it.  You might notice, from the `Content-Type` header, that the message body is in **msgpack format**.  This is a slightly more efficient format than JSON.  To encode the `logData` regular JavaScript object in msgpack format, we use the `msgpackr` package, imported in line `2`.  The final piece to note is that you will need to add your `LOGTAIL_SOURCE_TOKEN` to your `.env` file, so that you can import it in line `4`.

## 🖥️ Astro JS Middleware Example: Frontend

You can update the response in your middleware, perhaps replacing template parameters.  We might use this technique to vary content by region or time of day, and can do so just by altering the returned response from within the middleware.  Another way of adding new data in middleware is using the `locals` object.  In the middleware code, we added the timestamp there:

```typescript
locals.timestamp = timestamp;
```

We can access that value in individual `.astro` files.  Here is an example:

```astro
---
const { timestamp } = Astro.locals as { timestamp: string };
---

<html lang="en-GB">
	<head> <!-- TRUNCATED... -->
	</head>

	<body>
		<main class="container">
			<section class="content">{timestamp}</section>
		</main>
	</body>
</html>
```

In line `2`, we pull our `timestamp` off `Astro.locals`.  Note, this could be any arbitrary value we decided to include in the middleware function, though it should be possible to serialize the value as JSON.  Finally, that value is now available for use in markup, as in line&nbsp;`11`.


![Astro JS Middleware Example: Screen capture shows a browser window with a timestamp in large text.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/45lbr4g8jhhodb46ontm.png)

Interestingly, middleware works on static as well as SSR sites.  For a static site, though, the value displayed here would be the time stamp at build time, rather than at the point the request was made.  This is still useful, though, for example to include build time in a page footer.

## 🙌🏽 Astro JS Middleware Example: Wrapping Up

In this post, we saw an example of using the Astro middleware API.  In particular, we saw:

- how to **add middleware to your Astro site**;
- how you might use Astro middleware for **request logging**; and
- using **Astro.locals** to pass out data from middleware for use in markup.


You can see the <a href="https://github.com/rodneylab/astro/tree/main/demos/astro-js-middleware">full code for this project in the Rodney Lab GitHub repo</a>. Also see the <a href="https://docs.astro.build/en/guides/middleware/">Astro docs</a> in case there have been some changes in this experimental API, since writing.  I do hope you have found this post useful! I am keen to hear how you will middleware in your next projects.  Also, let me know about any possible improvements to the content above.

## 🙏🏽 Astro JS Middleware Example: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="@ ask Rodney" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter, <a href="https://toot.community/@rodney">@rodney@toot.community</a> on Mastodon and also the <a aria-label="Contact Rodney Lab via Telegram" href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on Astro" href="https://rodneylab.com/tags/astro/">Astro</a> as well as <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/seo/">SEO</a>. Also, <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.