Changelog
Updated
4 min read

Announcing the Arcjet NestJS & Remix adapters

Arcjet security as code adapters for NestJS and Remix.

Announcing the Arcjet NestJS & Remix adapters

Arcjet helps developers protect their apps by making it easy to drop in critical security functionality like bot detection, personal information detection, and attack prevention.

Arcjet is integrated directly into your code using our SDKs - an approach we call security as code. This allows you to define rules that can be dynamically adapted based on the request context and take the decision and adjust your application logic based on the result.

We started with support for the Next.js framework which used our underlying Node.js SDK. The core functionality lives in the runtime SDK, which we extend with the framework specific adapters. The result is that using the Arcjet Next.js | SvelteKit | Node.js | ... SDK feels like programming against a native Next.js | SvelteKit | Node.js | ... API.

If we don't have a framework SDK you can still use the runtime language SDK, but it requires a bit more work.

So today we're announcing support for both NestJS and Remix which now have their own native Arcjet adapters.

Arcjet + NestJS

NestJS is a popular backend JS framework that has a great set of tools to build scalable server-side applications.

Arcjet can be integrated into NestJS in several places using NestJS guards or directly within a route controller:

  • Global guard: Applies Arcjet rules on every request, but does not allow you to configure rules per route. The Arcjet protect function is called for you inside the guard and you can’t access the response.
  • Per route guard: Allows you to configure rules per route, but requires you to add the guard to every route and has limited flexibility. The protect function is called for you inside the guard and you can’t access the response.
  • Within route: Requires some code duplication, but allows maximum flexibility because you can customize the rules and response. You call the protect function directly in the controller and can access the return Promise that resolves to an ArcjetDecision object.

Arcjet NestJS quick start example

This is an example of a per route NestJS guard with Arcjet bot detection rules added as a decorator on the route controller:

import { WithArcjetRules, detectBot } from "@arcjet/nest";
import { Injectable, Get } from "@nestjs/common";

// This would normally go in your controller file e.g.
// src/page/page.controller.ts
// Attaches the ArcjetGuard to the controller to protect it with the specified
// rules extended from the global rules defined in app.module.ts.
@WithArcjetRules([
  detectBot({
    mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
    // configured with a list of bots to allow from
    // https://arcjet.com/bot-list
    // Block all bots except the following
    allow: [
      "CATEGORY:SEARCH_ENGINE", // Google, Bing, etc
      // Uncomment to allow these other common bot categories
      // See the full list at https://arcjet.com/bot-list
      //"CATEGORY:MONITOR", // Uptime monitoring services
      //"CATEGORY:PREVIEW", // Link previews e.g. Slack, Discord
    ],
  }),
])
export class PageController {
  constructor(private readonly pageService: PageService) {}

  @Get()
  index() {
    return this.pageService.message();
  }
}

A NestJS controller (src/page/page.controller.ts) protected from bots with the Arcjet guard.

// This would normally go in your service file e.g.
// src/page/page.service.ts
@Injectable()
export class PageService {
  message(): { message: string } {
    return {
      message: "Hello world",
    };
  }
}

The service (src/page/page.service.ts) associated with the controller.

Get started with Arcjet & NestJS by following our NestJS quick start guide.

Other NestJS examples

A complete NestJS application using all the Arcjet features is available on GitHub. There are further examples showing using NestJS + Arcjet with GraphQL and the Fastify server.

Arcjet + Remix

Remix is a lightweight full stack web framework that brings in core web APIs with a focus on standards compliance and a resilient user interface.

Remix does not support middleware, instead they recommend calling functions directly inside the loader. Loaders execute before the page is loaded and are a great place to call Arcjet's request analysis.

Arcjet also supports Remix actions, which are usually used to handle non-GET requests e.g. a form POST.

For example, you might want to run bot detection on every GET page load, but use bot detection and email validation in an action handling a formPOST.

Arcjet Remix quick start example

This is an example of how you would detect bots in a Remix application by calling Arcjet within a loader:

import arcjet, { detectBot } from "@arcjet/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    detectBot({
      mode: "LIVE",
      // configured with a list of bots to allow from
      // https://arcjet.com/bot-list - all other detected bots will be blocked
      allow: [
        // Google has multiple crawlers, each with a different user-agent, so we
        // allow the entire Google category
        "CATEGORY:GOOGLE",
        "CURL", // allows the default user-agent of the `curl` tool
        "DISCORD_CRAWLER", // allows Discordbot
      ],
    }),
  ],
});

export async function loader(args: LoaderFunctionArgs) {
  const decision = await aj.protect(args);

  if (decision.isDenied()) {
    throw new Response("Forbidden", { status: 403, statusText: "Forbidden" });
  }

  return null;
}

If you were creating a form then you could combine this with calling Arcjet email validation within the Remix action:

import arcjet, { validateEmail } from "@arcjet/remix";
import type { ActionFunctionArgs } from "@remix-run/node";

const aj = arcjet({
  // Get your site key from https://app.arcjet.com and set it as an environment
  // variable rather than hard coding.
  key: process.env.ARCJET_KEY!,
  rules: [
    validateEmail({
      mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
      // block disposable, invalid, and email addresses with no MX records
      block: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"],
    }),
  ],
});

// The action function is called for non-GET requests, which is where you
// typically handle form submissions that might contain an email address.
export async function action(args: ActionFunctionArgs) {
  // The request body is a FormData object
  const formData = await args.request.formData();
  const email = formData.get("email") as string;

  const decision = await aj.protect(args, { email });
  console.log("Arcjet decision", decision);

  if (decision.isDenied()) {
    if (decision.reason.isEmail()) {
      return Response.json({ error: "Invalid email." }, { status: 400 });
    } else {
      return Response.json({ error: "Forbidden" }, { status: 403 });
    }
  }

  // We don't need to use the decision elsewhere, but you could return it to
  // the component
  return null;
}

Get started with Arcjet & Remix by following our Remix quick start guide.

There is also a Remix example app built to show how to use all of the Arcjet features available on GitHub.

Related articles

Subscribe by email

Get the full posts by email every week.