Skip to content

Server-side JSX/TSX rendering for your NestJS application

License

Notifications You must be signed in to change notification settings

pmb0/nestjs-tsx-views

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nestjs-tsx-views

React SSR module for NestJS MVC

Example

Controller:

import { Controller, Get, Render } from "@nestjs/common";
import { MyViewProps } from "./views/my-view";

@Controller()
export class AppController {
  @Get()
  @Render("my-view")
  index(): MyViewProps {
    return { name: "world" };
  }
}

views/my-view.tsx:

import React, { ReactElement } from "react";
import { MainLayout } from "./layouts/main";

export interface MyViewProps {
  name: string;
  title: string;
}

const MyView = ({ name, ...props }: MyViewProps): ReactElement => (
  <div>Hello {name}</div>
);

export default MyView;

Highlights

  • Fast, since the JSX/TSX files do not have to be transpiled on-the-fly with every request
  • Separate NestJS modules can use their own views directories (see multi module example)
  • Works with compiled files (.js / node) and uncompiled files (.tsx / ts-node, ts-jest, ...)
  • Provides React contexts
  • Supports execution of GraphQL queries from JSX components

Table of contents

Usage

$ npm install --save nestjs-tsx-views

Import the module with TsxViewsModule.register(...) or TsxViewsModule.registerAsync(...).

Synchronous configuration

Use TsxViewsModule.register(). Available options are described in the TsxViewsModuleOptions interface.

@Module({
  imports: [
    TsxViewsModule.register({
      viewsDirectory: resolve(__dirname, "./views"),
      prettify: true,
      forRoutes: [AppController],
    }),
  ],
})
export class MyModule {}

Asynchronous configuration

If you want to use retrieve you TSX views options dynamically, use TsxViewsModule.registerAsync(). Use useFactory and inject to import your dependencies. Example using the ConfigService:

@Module({
  imports: [
    TsxViewsModule.registerAsync({
      useFactory: (config: ConfigService) => ({
        viewsDirectory: resolve(__dirname, './views'),
        prettify: config.get('PRETTIFY_HTML'
        )
        forRoutes: [AppController],
      }),
      inject: [ConfigService],
    }),
  ],
})
export class MyModule {}

React Context

  1. Define a React context:
import { createContext } from 'react'

export interface MyContextProps {
  name: string
}

export const MyContext = createContext<MyContextProps | undefined>
  1. Set the context in your controller (or provider):
@Controller()
export class AppController {
  constructor(private readonly ssr: TsxViewsService) {}

  @Get()
  @Render("my-view")
  index() {
    this.#ssr.addContext(MyContext, { name: "My context data" });

    return {};
  }
}
  1. Use it somewhere in your component:
import { useContext } from "react";
import { MyContext } from "./my-context";

export function MyComponent() {
  const { name } = useContext(MyContext);
  return <span>Hallo, {name}!</span>;
}

GraphQL

This module supports the execution of GraphQL queries from the TSX template. For this purpose graphql, @apollo/client and cross-fetch have to be installed separately:

$ npm install --save @apollo/client cross-fetch graphql

See example/graphql/app.module.ts for a working example of how to configure the NestJS module. View example:

// example/graphql/views/my-view.tsx

export interface Film {
  id: string;
  title: string;
  releaseDate: string;
}

export interface AllFilms {
  allFilms: {
    films: Film[];
  };
}

const MY_QUERY = gql`
  query AllFilms {
    allFilms {
      films {
        id
        title
        releaseDate
      }
    }
  }
`;

export interface MyViewProps {
  name: string;
  title: string;
}

const MyView = (props: MyViewProps): ReactElement => {
  const { data, error } = useQuery<AllFilms>(MY_QUERY);

  if (error) {
    throw error;
  }

  return (
    <MainLayout {...props}>
      <h2>Films:</h2>
      {data?.allFilms.films.map((film) => (
        <ul key={film.id}>
          {film.title} ({new Date(film.releaseDate).getFullYear()})
        </ul>
      ))}
    </MainLayout>
  );
};

export default MyView;

Configuration

nestjs-tsx-views can be configured with the following options:

export interface TsxViewsModuleOptions extends ReactViewsOptions {
  /**
   * The directory where your views (`.tsx` files) are stored. Must be
   * specified.
   */
  viewsDirectory: string;

  /**
   * [Doctype](https://developer.mozilla.org/en-US/docs/Glossary/Doctype) to
   * be used. */
  doctype?: string;

  /**
   * If activated, the generated HTML string is formatted using
   * [prettier](https://github.com/prettier/prettier)
   */
  prettify?: boolean;

  /**
   * With this optional function the rendered HTML document can be modified. For
   * this purpose a function must be defined which gets the HTML `string` as
   * argument. The function returns a modified version of the HTML string as
   * `string`.
   */
  transform?: (html: string) => string | Promise<string>;

  /**
   * Excludes routes from the currently processed middleware.
   *
   * @param {(string | RouteInfo)[]} routes
   * @returns {MiddlewareConfigProxy}
   */
  exclude?: (string | RouteInfo)[];

  /**
   * Attaches passed either routes or controllers to the currently configured middleware.
   * If you pass a class, Nest would attach middleware to every path defined within this controller.
   *
   * @param {(string | Type | RouteInfo)[]} routes
   * @returns {MiddlewareConsumer}
   */
  forRoutes?: (string | Type<Controller> | RouteInfo)[];
}

License

nestjs-tsx-views is distributed under the MIT license. See LICENSE for details.

About

Server-side JSX/TSX rendering for your NestJS application

Resources

License

Stars

Watchers

Forks

Packages

No packages published