BeerMoneyDev / nest-remix

An interop layer between NestJS and Remix
MIT License
51 stars 1 forks source link

nest-remix


An interop layer between NestJS and Remix


NPM Version Package License NPM Downloads

Features

Overview

Check out this video on YouTube about NestJS, Remix, and the goal of nest-remix: Supercharged NestJS React Rendering with Remix

How To Use

Install

Using NestJS Schematics

This will:

nest add nest-remix

Manually

Details coming soon.

Basic Usage

Setup and Configuration

To add the interop layer, import the RemixModule from nest-remix into your NestJS application module. The RemixModule takes a configuration object with the following properties

// before
import { AppService } from './app.service';
import { AppController } from './app.controller';
import { Module } from '@nestjs/common';

@Module({
  controllers: [AppController],
  providers: [AppService],
  exports: [AppService],
})
export class AppModule {}

// after
import * as path from 'path';

import { AppService } from './app.service';
import { AppController } from './app.controller';
import { Module } from '@nestjs/common';
import { RemixModule } from 'nest-remix';

@Module({
  imports: [
    RemixModule.forRoot({
      publicDir: path.join(process.cwd(), 'public'),
      browserBuildDir: path.join(process.cwd(), 'build/'),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
  exports: [AppService],
})
export class AppModule {}

Adding Remix routes

Add all your Remix routes to src/app/routes as you would in a typical Remix application.

Using NestJS services as action and loader functions

The wireLoader and wireAction functions connect to the RemixModule-decorated module to get providers. By supplying these functions with the type to be used as the backend, nest-remix will route the request appropriately given the @Loader() and @Action() decorators. Services can be injected into the backend class as expected given their module hierarchy.

It is required to create a new file as the NestJS decorators will attempt to execute as client-side code otherwise, breaking your build. As such, you will need two files for a route now (sorry!) - {your-route}.tsx and {your-route}.server.ts (you can name it whatever you want, but this is the recommended naming convention).

Note: backends must be provided/exported to be accessible by the RemixModule-decorated module.

// src/app/routes/hello-world.tsx
import type { ActionFunction, LoaderFunction } from '@remix-run/node';
import { Form, useActionData, useLoaderData } from '@remix-run/react';
import { wireAction, wireLoader } from 'nest-remix/core.server';
import { HelloWorldBackend } from './hello-world.server';

export const loader: LoaderFunction = (args) =>
  wireLoader(HelloWorldBackend, args);

export const action: ActionFunction = (args) =>
  wireAction(HelloWorldBackend, args);

export default function HelloWorld() {
  const { message } = useLoaderData<HelloWorldBackend['getMessage']>();
  const actionData = useActionData<
    HelloWorldBackend['setMessage'] | HelloWorldBackend['setMessageFallback']
  >();

  return (
    <div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
      <h1>Welcome to Remix</h1>
      <div style={{ marginTop: 20 }}>{actionData?.newMessage || message}</div>
      <fieldset style={{ marginTop: 20 }}>
        <legend>Update the message</legend>
        <Form method="post">
          <input type="text" name="message" defaultValue={''} />
          <button>Post update</button>
        </Form>
        <Form method="put">
          <input type="text" name="message" defaultValue={''} />
          <button>Put update</button>
        </Form>
      </fieldset>
    </div>
  );
}
// src/app/routes/hello-world.server.ts
import { Body, Injectable, ParseIntPipe, Query } from '@nestjs/common';
import { LoaderFunctionArgs } from '@remix-run/node';
import { Action, Loader, RemixArgs } from 'nest-remix';
import { AppService } from './app.service.ts';

@Injectable()
export class HelloWorldBackend {
  constructor(private readonly appService: AppService);

  @Loader()
  getMessage(
    @Query('defaultMessage') defaultMessage: string,
    @Query('counter', ParseIntPipe) _counter: number,
    @RemixArgs() _remixArgs: LoaderFunctionArgs
  ) {
    return { message: defaultMessage || this.appService.getDefaultMessage() };
  }

  @Action()
  async setMessageFallback(@Body() body: { message: string }) {
    return { newMessage: body.message + ' [POST, DELETE]' };
  }

  @Action.Put()
  async setMessage(@Body() body: { message: string }) {
    return { newMessage: body.message + ' [PUT]' };
  }
}
action Routing

The @Action() decorator will capture all requests to the wired action function. Additional routing is possible by using @Action.Post(), @Action.Put(), and @Action.Delete(). It will always fall back to @Action() if an HTTP verb is not supplied.

Available routing decorators
Function decorators
Parameter decorators
Accessing the Remix args

nest-remix provides a custom decorator, @RemixArgs(). This provides the LoaderArgs or ActionArgs depending on the type of function.

NestJS pipes in @Action() and @Loader() functions

Pipes should work as expected. They are applied to the NestJS request object, NOT the Remix request. But, the Remix request object is accessible via @RemixArgs().

How it works

@RemixModule appends a custom controller (RemixController) which handles the @All('*') route at the root. This controller is at the end of the pipeline, so NestJS will attempt to server the request before it falls back to Remix. During this process, we use the NestJS module ref to provide injectable services for Remix backends.

Important notes

Use nest-remix/core.server in your route files

As described in the remix.run "gotcha's" page, it is important to use .server-suffixed files from your routes to avoid server-side rendering attempts on your client-side code. A fortunate side-effect of nest-remix is that this pattern allows you to funnel all server-side code through nest-remix/core.server and {your-route}.server.ts, inherently protecting you.

Stay In Touch

License

nest-remix is MIT licensed.