Create Your Own tRPC Stack!
Tags: webdev, tutorial, react, tailwindcss
Published: 2023-01-26 15:38:55

---
title: Create Your Own tRPC Stack!
published: true
description: Use Nx features to create and solidify your own tech stack!
tags: webdev, tutorial, react, tailwind
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f9nn2hzxiwkirsblx63x.png
canonical_url: https://blog.nrwl.io/whats-new-with-lerna-6-5-4a4f69371504source=friends_link&sk=b881d7e5265c230e19c3f692ca5ea816
# Use a ratio of 100:42 for best results.
# published_at: 2023-01-13 23:58 +0000
---

## Table of Contents
- [The Evolution of Awesome Tec Stacks](#evolution)
- [Building Our Concrete Example Stack](#create-plugin)
- [Creating a Code Generation Script](#create-generator)
- [Using that Script](#using-a-generator)
- [Defining a Task Executor](#create-executor)
- [Using that Executor](#using-an-executor)
- [Putting it All Together](#putting-it-all-together)


## The Evolution of Awesome Tech Stacks <a name="evolution" /> 

### LAMP - the OG Tech Stack

Arguably the first tech stack that was called as such was [the LAMP stack](https://en.wikipedia.org/wiki/LAMP_software_bundle), which was traced back to a 1998 issue of the German computing magazine: Computertechnik. 

LAMP here was an acronym that stood for:

L - Linux (the operating system)
A - Apache (the HTTP server)
M - MySql, or sometimes MariaDB (the database)
P - PHP / Perl / Python (the programing language)

![The LAMP stack](https://hackmd.io/_uploads/B1ZBHHRij.png)

There was something magical about this idea of a tech stack. This bundle of free-to-use, open-sourced, and relatively interchangeable software was a viable (maybe even preferable!) alternative to the many paid, proprietary, and "locking-in" packages that were popular at the time, and gave rise to some killer applications - like WordPress!

An interesting essential part of this idea of "a stack" was the concept of interchangeable pieces - note how the "P" in particular could stand for 3 VERY different programming languages, and a close cousin to LAMP was [WAMP](https://en.wikipedia.org/wiki/LAMP_(software_bundle)#WAMP) which exchanged Linux for Windows.

### First-Gen JS stacks: MEAN/MERN

As we move on from the early 2000s to the 2010s - the JavaScript ecosystem, in particular, started growing in popularity, spurred by the creation of [Node.js](https://nodejs.org/en/) in 2009, which was accompanied by a JS package manager: [npm](https://www.npmjs.com/about).

With this popularity came the rise of the first all-JS stack - the MEAN stack, which stood for:

M - Mongo - a [JSON (JavaScript Object Notation) based Database](https://www.mongodb.com/databases/json-database)
E - [Express](https://expressjs.com/) - a Web Framework for Node.js
A - [AngularJS](https://angularjs.org/) - a frontend JS web framework
N - [NodeJS](https://nodejs.org/en) - a backend Javascript runtime environment

![The MEAN Stack](https://hackmd.io/_uploads/HyV2mrRii.png)

A later evolution of this stack was the MERN stack that exchanged [React](https://reactjs.org/) with AngularJS as the frontend web framework.

![The MERN Stack](https://hackmd.io/_uploads/BkcJNHAsi.png)

While this first generation of JavaScript stacks made for a recognizable and searchable term for describing a typical setup, there was little in the way of standardizing this stack.

### Current-Gen JS stacks: T3, tRPC, Tanstack

Stepping into the current generation of JavaScript in the 2020s - and while we're still early in this generation of JS stacks, a clear trend can be seen in the form of a focus on tooling and the developer experience.

A stand-outs from this generation of the JS stack is [the t3 stack](https://create.t3.gg), which is an opinionated and evolving collection of the following technologies:

![Taken from https://create.t3.gg](https://hackmd.io/_uploads/r1qFRBAoj.png)

A marked difference we can see from the MEAN/MERN stacks of the previous generation is that while MEAN/MERN operated mainly as a convenient search term to give needed context to looking up problems, the t3 stack is much more formalized, including a website, a community of support on discord and Twitter, and importantly: [a CLI tool](https://www.npmjs.com/package/create-t3-app) to create a working starter application.

This CLI is exciting as it is a way of standardizing and versioning the stack over time. If the community identifies a specific tool or enhancement that it can canonically bring into the stack, the "generative" nature of this tool gives developers a working starting point. Still, they can work from there to bring in their tools to further customize their stack.

The impact on the developer, however, is the ultimate goal here, as the t3 tenants of ruthless practicality:

Developers should be focused on solving their problems.

Using [`create-t3-app`](https://www.npmjs.com/package/create-t3-app) clearly demonstrates this: after answering a short series of questions, the developer has an operational full-stack application that they can see in action with one simple command to start up the project locally.

Other popular packages in this generation include the [tanstack series of packages](https://tanstack.com/):

[![tanstack](https://hackmd.io/_uploads/Bk0MHUAij.png)](https://tanstack.com/)

and [tRPC](https://trpc.io/):

[![tRPC](https://hackmd.io/_uploads/SksOrUAoj.png)](https://trpc.io/)

While not fully-fledged stacks the way t3 is, these projects are likely successful mainly due to the attention given to tooling - particularly type safety. This positions them as obvious building blocks for other stacks to incorporate.

### The Next Step: Nx

We think that [Nx](https://nx.dev) is uniquely positioned to be an essential tool in this current generation of JS stacks.

![](https://hackmd.io/_uploads/ryte9UCjj.gif)

Nx is built on a series of core tooling features that benefit most repositories (especially [monorepos](https://monorepo.tools/)), including:

+ code generation mechanisms that allow you to quickly scaffold an entire project structure or add features such as Tailwind to an existing project
+ a way of visualizing how sections of your code are connected via [the project graph](https://nx.dev/core-features/explore-graph)
+ mechanisms to ensure speed while just running commands via [the `nx affected` command](https://nx.dev/concepts/affected) and [task caching](https://nx.dev/core-features/cache-task-results)
+ a mechanism for defining how commands in your workspace depend on each other via [task pipeline configuration](https://nx.dev/concepts/task-pipeline-configuration)
+ a way to easily extend tooling via a plugin mechanism

While all the above features help make things faster for any project, Nx's pluggability is particularly interesting for our discussion about stacks.

[![Official Nx Packages List](https://hackmd.io/_uploads/HkQdnLRii.png)](https://nx.dev/packages)

At Nx, we maintain many packages that are **designed to be used as building blocks for developers to create their own stacks**.

Framework-based packages like our React, Angular, Nest, and Node packages offer developers a way of assembling their own stack based on their preferences. For instance, adding a react application to your repository with react-router-dom installed and pre-configured to match their getting started guide is as simple as running the command:

```
npx nx g @nrwl/react:app my-app --routing
```

Then to complement this frontend application with a backend written with NestJS, you run the command:

```
npx nx g @nrwl/nest:app api --frontendProject=my-app
```

This will not only create a backend Nest application for you but also **configure your React application to create a proxy for your API** requests to this Nest application. This otherwise tedious task will often waste developer time.

Other tooling-based packages like our Vite, Webpack, and Rollup packages offer support for build, test, and Linting level tools allowing easy migration to the tool that best suits your preferences, with the added benefit of allowing for painless transitions from using webpack to vite or vice-versa.

In addition to the official Nx packages comes a registry of community-supported plugins that support other frameworks, tools, and languages that we don't have official support for:

[![Community Plugin Registry](https://hackmd.io/_uploads/HkJoGPRso.gif)](https://nx.dev/community#plugin-directory)

These community-supplied plugins offer more building blocks to create your desired stack.

And our [Nx plugin package](https://nx.dev/packages/nx-plugin) provides an API for creating your own building blocks and composing other building blocks into a pre-defined stack - just like the t3 stack!

### Building Our Concrete Example Stack

With this context in mind, let's dive into creating our concrete stack, specifically:

+ React as our frontend Application
+ Tailwind for styling on our frontend app
+ Express for our backend application
+ Vite and Vitest for frontend bundling and testing
+ Esbuild for backend building
+ tRPC for building our API and full-stack type safety across both apps

We can build upon Nx's official plugins for the first five items and write our generators for the tRPC pieces based on their "Getting Started" documentation.

The end goal is to have a codified and versioned stack that will allow us to create a full-stack application in one command and then be able to serve the entire working stack in a single command.

Here's a teaser of how this will all look when we're finished:

{%youtube 2YwD6HRFI1I %}

[And you can see and clone the workspace we'll create here.](https://github.com/nrwl/nx-recipes/tree/main/plugin)

## Creating our Plugin <a name="create-plugin"></a>

Before starting this section, create an initial workspace using the command `create-nx-workspace@latest nx-trpc-example --preset=empty`, and then install our dependencies by running the commands:

```bash
> yarn add -D @nrwl/react @nrwl/vite @nrwl/node @nrwl/nx-plugin @nrwl/esbuild
> yarn add @trpc/client @trpc/server
> npx nx g @nrwl/react:init
> npx nx g @nrwl/vite:init
> npx nx g @nrwl/node:init
> npx nx g @nrwl/esbuild:init
```

> We'll need the above to install manually, but in the sequel to this guide, we'll create our own `init` generator script so that any consumers of our package won't need to run this step manually!

Next, to create our plugin, we'll run the command:

```
npx nx g @nrwl/nx-plugin:plugin plugin --minimal
```

This will use [the `plugin` generator of the `@nrwl/plugin` package](https://nx.dev/packages/nx-plugin/generators/plugin) to create a plugin for our workspace named: `plugin`. We're also using the `--minimal` flag here, so the plugin is empty initially.

Once this command is run, we'll see the new project in: `libs/plugin`

![](https://hackmd.io/_uploads/B1peFv0ii.png)

The scaffolding of this project is separated into `executors` and `generators` - where `executors` are an Nx mechanism to define a task (like building an app or starting a web server for local development)  simplified to a simple Typescript function, and `generators` are an Nx mechanism for code generation simplified to a simple Typescript function. We'll see these more in the following sections, starting with `generators`:

## Create a Full-Stack Application Generator <a name="create-generator"></a>

Nx's Generator API simplifies any code-gen script to a simple function. The `@nrwl/devkit` package goes on to provide utility functions to make the more burdensome things about code-generation scripts more manageable and tolerable!

When we consider at a high level what our code generation should look like, we can separate it into these four steps:

+ generate a front-end application
+ generate a corresponding backend application
+ generate a tRPC server library that is already imported into our backend application
+ generate a tRPC client library that is already imported by our frontend application

Because first-party Nx generators from the official Nx packages are functions as well, we can create a corresponding function for all four of the points above and then call those Nx generators in sequence to implement our generator.

Now that we have our roadmap for the generator, we also need to consider how we'll want to parameterize our generator. The Nx CLI uses a schema-based approach to defining these options so that when another developer uses our code generation script, they can pass any of these options to the CLI, which will be passed as parameters to our generator function we'll write next.

Given this, let's limit our options for now to the following:

+ the name of the full-stack application. This will be required, and we'll use this name to inform how we'll name the four projects to generate for this app: (`${name}-web`, `${name}-server`, `${name}-trpc-server` and `${name}-trpc-client`).
+ the port for the backend and the frontend applications to run on. These won't be required - and we'll default them to 3000 and 3333, respectively.

With these in mind, we'll create our application generator using the command:

```
npx nx g @nrwl/nx-plugin:generator app --project=plugin
```

Which will use [the `generator` generator of the `@nrwl/nx-plugin` package](/packages/nx-plugin/generators/generator) to create our new generator named "app" located at `libs/plugin/src/generators/app`.

Once the command is run, you should see new files located in [`libs/plugin/src/generators/app`](https://github.com/nrwl/nx-recipes/tree/main/trpc-example-stack/libs/plugin/src/generators/app), and metadata for this new `app` generator in [`libs/plugin/generators.json`](https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/generators.json).

The file: `libs/plugin/generators.json` will define all the generators contained in our plugin to Nx - but we won't need to touch this file as the generator already took care of any changes required.

As for the other files created, we'll start with [`libs/plugin/src/generators/app/schema.d.ts`](https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/src/generators/app/schema.d.ts):

```typescript
export interface AppGeneratorSchema {
  name: string;
  frontendPort?: number;
  backendPort?: number;
}
```

This interface matches the parameterization of the generator we want to support mentioned above. We'll also want to adjust the [`libs/plugin/src/generators/app/schema.json`](https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/src/generators/app/schema.json) file to match this as well:

{% embed https://gist.github.com/ZackDeRose/69321a120ee78844a52831ec07ffe6b2 %}

This schema file will inform all Nx tools (including the CLI validation, the CLI `--help` option, and the Nx Console tool) what options are available for this generator.

Notice that the `name` property is the only required parameter, and lines 11-14 above tell us that it is the first non-named option in our command's argument vector (or `argv`).

```bash
npx nx generate @nx-trpc-example/plugin:app test
```

So, for example, the above command would create our new application with the name: "test", and `frontendPort` and `backendPort` would use their default values. We can add specific values for these options by providing it to our command like so:

```bash
npx nx generate @acme-dev/plugin:app test --frontendPort=3001
```

Next, we can remove the `libs/plugin/src/generators/app/files` directory, as we won't use that mechanism for this generator.

> The `@nrwl/nx-plugin` creates this `files` directory to support templating with Nx generators via the `writeFiles()` function created in the `generator.ts` file.
> The generators we're creating in this article are relatively simple, so we'll use string literals when changing a file's contents.

The implementation of our generator will be written in the `libs/plugin/src/generators/app/generator.ts` file.

The mental model here is we will implement these steps in the following function:

```typescript
import { Tree } from '@nrwl/devkit';
import { AppGeneratorSchema } from './schema';

export default async function (tree: Tree, options: AppGeneratorSchema) {
    // ... implementation to go here!
}
```

Our `tree` parameter represents a virtual file system of the current state of our repo. We'll use this `Tree` API and utility functions from the `@nrwl/devkit` package to mutate that `tree` object until it matches the desired state.

You can [find the entire contents of that file here](https://github.com/nrwl/nx-recipes/blob/main/trpc-example-stack/libs/plugin/src/generators/app/generator.ts), and you can see our original high-level four-step approach to the generator:

```typescript
export default async function (tree: Tree, options: AppGeneratorSchema) {
  // ...
  await createReactApplication(tree, optionsWithDefaults, webAppName);
  await createNodeApplication(
    tree,
    optionsWithDefaults,
    serverName,
    webAppName
  );
  await createTrpcServerLibrary(tree, optionsWithDefaults, trpcServerName);
  await createTrpcClientLibrary(tree, optionsWithDefaults, trpcClientName);
}
```

That's most of our high-level view of what a generator is and how to create it - you can [skip ahead to using the generator](#using-a-generator), but otherwise - let's dive in deeper by walking through the implementation of each of these functions next:

### 0. Adding Default Options and Project Names

As a preliminary step, we'll define the default values for the optional parameters (lines 4-7 below). Then we'll create our `optionsWithDefaults` by spreading the `defaultPorts` with the `options` coming in from the command. This has the effect of overwriting the `defaultPorts` if the user provides their own, but otherwise, the defaults are used.

{% embed https://gist.github.com/ZackDeRose/92e7e8a55a4dca79e2207ab03a5ec887 %}

We'll also import the `names` function from the `@nrwl/devkit` package so that we can kebab-case the name provided by the user. For example, if the user-provided name is "helloWorld", the `fileName` of this will be "hello-world". We'll use this to get a name of our four projects in kebab-case to use later on in the generator.

> Other cases supported by the `names()` function include:
> + pascal case
>   + `console.log(names('helloWorld').className); // HelloWorld`
> + camel case:
>   + `console.log(names('helloWorld').propertyName); // helloWorld`
> + screaming snake case:
>   + `console.log(names('helloWorld').constantName); // HELLO_WORLD`
> + untouched:
>   + `console.log(names('uNtOuChEd').name); // uNtOuChEd`
>
> We'll use some of these later on.

### 1. Create Our React App

For this step, we'll create a function called `createReactApplication()`:

```typescript
async function createReactApplication(
  tree: Tree,
  options: AppGeneratorSchema,
  webAppName: string
) {
  // implementation will go here!!
}
```

Our first step in this function will be to use Nx's first-party React app generator. We can import it like so:

```typescript
import { applicationGenerator as reactAppGenerator } from '@nrwl/react';
```

> Notice we're renaming `applicationGenerator` to `reactAppGenerator` because we'll be importing other `applicationGenerator`s in future steps!

When it comes time to call the function, we'll call it like so:

{% embed https://gist.github.com/ZackDeRose/0bde83f87ffd21dbc90690a7c4c83479 %}

> Note that Typescript Intellisense can help us with the required and optional options in lines 7-13 above!
> 
> ![](https://hackmd.io/_uploads/rkxhWdAjj.png)

The options we've provided will set up the application with vite and vitest, and we'll give it the appropriate port. Note that if we ever wanted to adjust our bundler to webpack in the future, making that change is as simple as changing the bundler option here!

Also note that since the `reactAppGenerator` we imported is an `async function`, we want to make sure we `await` it! Without awaiting, we could get a race condition that would give us interesting and undesired results.

> It may be a good idea to test our generator out incrementally. For example, at this point, we can confirm that our React application is generated as expected.
> To do this, I recommend using git to commit all changes you've made so far right before running the generator:
> ```bash
> npx nx g @nx-trpc-example/plugin:app test
> ```
> You can check that things look good and then reset to discard all the results so you can continue building the generator:
> ```bash
> git add . && git reset --hard HEAD
> ```
> Another tool you have for previewing changes from your generator is the `--dry-run` option. This will list all files that would have been created, updated, or deleted by the generator without actually running them:
> ```bash
> npx nx g @nx-trpx-example/plugin:app test --dry-run
> ```

The Nx React plugin includes a generator for adding Tailwind to a React application, so we'll import it and call it next:

```typescript
import { setupTailwindGenerator } from '@nrwl/react';

// ...

async function createReactApplication(
  tree: Tree,
  options: AppGeneratorSchema,
  webAppName: string
) {
  await reactAppGenerator(tree, {
    name: webAppName,
    linter: Linter.EsLint,
    style: 'css',
    e2eTestRunner: 'none',
    unitTestRunner: 'vitest',
    bundler: 'vite',
    devServerPort: optionsWithDefaults.frontendPort,
  });
  await setupTailwindGenerator(tree, { project: webAppName });
  // rest of implementation to come here!!
}
```

Our next step is to add the boilerplate to import the tRPC client (that we'll generate in step 4!) to our new `app.tsx` file of our React application:

```typescript
import { getWorkspaceLayout, names, Tree } from '@nrwl/devkit';

// ...

function createAppTsxBoilerPlate(tree: Tree, name: string) {
  const { className, fileName } = names(name);
  const { npmScope } = getWorkspaceLayout(tree);

  const appTsxBoilerPlate = `import { create${className}TrpcClient } from '@${npmScope}/${fileName}-trpc-client';
import { useEffect, useState } from 'react';

export function App() {
  const [welcomeMessage, setWelcomeMessage] = useState('');
  useEffect(() => {
    create${className}TrpcClient()
      .welcomeMessage.query()
      .then(({ welcomeMessage }) => setWelcomeMessage(welcomeMessage));
  }, []);
  return (
    <h1 className="text-2xl">{welcomeMessage}</h1>
  );
}

export default App;
`;
  tree.write(`apps/${fileName}-web/src/app/app.tsx`, appTsxBoilerPlate);
}
```

Notice that we're anticipating our import statement to match the trpc-client library that we'll create in Step 4 and using the client to query for a `welcomeMessage` that we'll define in our trpc-server library in Step 3.

Also, note that we use the `write` method of the `Tree` api here to write a file to our virtual file system.

> Other methods on the `Tree` api include:
> - `read()`
> - `exists()`
> - `delete()`
> - `rename()`
> - `isFile()`
> - `children()`
> - `listChanges()`
> - `changePermissions()`
>
> The `@nrwl/devkit` also includes a list of utility functions to manipulate our `tree` more deftly. You can find the [whole list here](https://nx.dev/packages/devkit/documents/index#functions)

We're also using the [`getWorkspaceLayout` function](https://nx.dev/packages/devkit/documents/index#getimportpath) of the devkit so that we can match the correct import scope that Nx uses by default for Typescript imports. 

Our last step for now on the React application is to adjust the default port to match the port provided by the user:

{% embed https://gist.github.com/ZackDeRose/9bdfe23ce431292cb5f1d2483948b0fc %}

We're also using the [`updateJson` utility function](https://nx.dev/packages/devkit/documents/index#updatejson) from the `@nrwl/devkit` to update our React app's `project.json` file to change the `options.port` of our `serve` target to the provided port (line 15 above).

With these pieces now created, we can finish our `createReactApplication` function now:

```typescript
async function createReactApplication(
  tree: Tree,
  options: AppGeneratorSchema,
  webAppName: string
) {
  await reactAppGenerator(tree, {
    name: webAppName,
    linter: Linter.EsLint,
    style: 'css',
    e2eTestRunner: 'none',
    unitTestRunner: 'vitest',
    bundler: 'vite',
    devServerPort: options.frontendPort,
  });
  await setupTailwindGenerator(tree, { project: webAppName });
  createAppTsxBoilerPlate(tree, options.name);
  adjustDefaultDevPort(tree, options);
}
```

### 2. Create Our Backend Node App

We'll take a similar approach to create our Node application, starting by importing the `@nrwl/node` application generator:

{% embed https://gist.github.com/ZackDeRose/c742f68238ef0c30a0079cd10964f81c %}

Notice that we are adding a `frontendProject` to the options (line 19 above). This will add a proxy configuration to our React app's development server - allowing us to side-step CORS complications in our local environment.

We'll also adjust the contents of the `main.ts` file that is already created after the Promise returned by this `nodeAppGenerator()` resolves:

```typescript
function createServerBoilerPlate(
  tree: Tree,
  name: string,
  backendPort: number
) {
  const { fileName } = names(name);
  const { npmScope } = getWorkspaceLayout(tree);
  const serverBoilerPlate = `/**
 * This is not a production server yet!
 * This is only a minimal backend to get started.
 */

import express from 'express';
import * as trpcExpress from '@trpc/server/adapters/express';
import { trpcRouter } from '@${npmScope}/${fileName}-trpc-server';
import { environment } from './environments/environment';

const app = express();

app.use('/api', trpcExpress.createExpressMiddleware({ router: trpcRouter }));

const port = environment.port;
const server = app.listen(port, () => {
  console.log(\`Listening at http://localhost:\${port}/api\`);
});
server.on('error', console.error);

`;
  tree.write(`apps/${name}-server/src/main.ts`, serverBoilerPlate);
  tree.write(
    `apps/${name}-server/src/environments/environment.ts`,
    `export const environment = {
  production: false,
  port: ${backendPort},
};
`
  );
}
```

Note that we're anticipating an import of the `trpcRouter` that we'll generate in step 3, and we're also using the `backendPort` from our options to write this port to the `environment.ts` file, and thereby configure our backend port.

Putting these pieces together, our resulting function looks like so:

```typescript
async function createNodeApplication(
  tree: Tree,
  options: AppGeneratorSchema,
  serverName: string,
  webAppName: string
) {
  await nodeAppGenerator(tree, {
    name: serverName,
    js: false,
    linter: Linter.EsLint,
    unitTestRunner: 'none',
    pascalCaseFiles: false,
    skipFormat: true,
    skipPackageJson: false,
    frontendProject: webAppName,
  });
  createServerBoilerPlate(tree, options.name, options.backendPort);
}
```

### 3. Create a Library for Our tRPC Server

We'll use a similar approach with the `@nrwl/js` library generator for this lib:

```typescript
import { libraryGenerator as jsLibGenerator } from '@nrwl/js';

// ...

async function createTrpcServerLibrary(
  tree: Tree,
  options: AppGeneratorSchema,
  trpcServerName: string
) {
  await jsLibGenerator(tree, {
    name: trpcServerName,
    bundler: 'vite',
    unitTestRunner: 'vitest',
  });
  // ... rest of the implementation to go here!
}
```

After the library is generated, we'll add the boilerplate to export our trpc from the `index.ts` file created in this library:

```typescript
function createTrpcServerBoilerPlate(tree: Tree, name: string) {
  const { className } = names(name);
  const trpcServerBoilerPlate = `import { initTRPC } from '@trpc/server';

const t = initTRPC.create();
  
export const trpcRouter = t.router({
  welcomeMessage: t.procedure.query((req) => ({
    welcomeMessage: \`Welcome to ${name}!\`,
  })),
});

export type ${className}TrpcRouter = typeof trpcRouter;
`;
  tree.write(`libs/${name}-trpc-server/src/index.ts`, trpcServerBoilerPlate);
}
```

Note that this creates the `welcomeMessage` query that we call from `app.tsx` back in step 1!

After calling these functions, we'll make a few more adjustments to clean up this library:

+ remove the ${libraryName}.ts file that was generated
+ remove the ${libraryName}.spec.ts file that was generated
+ add a sourceRoot property to our `project.json` file

These operations are simple enough that we can inline them in our `createTrpcServerLibrary`:

```typescript
async function createTrpcServerLibrary(
  tree: Tree,
  options: AppGeneratorSchema,
  trpcServerName: string
) {
  await jsLibGenerator(tree, {
    name: trpcServerName,
    bundler: 'vite',
    unitTestRunner: 'vitest',
  });
  createTrpcServerBoilerPlate(tree, options.name);
  tree.delete(`libs/${trpcServerName}/src/lib/${trpcServerName}.ts`);
  tree.delete(`libs/${trpcServerName}/src/lib/${trpcServerName}.spec.ts`);
  updateJson(tree, `libs/${trpcServerName}/project.json`, (json) => ({
    ...json,
    sourceRoot: `libs/${trpcServerName}/src`,
  }));
}
```

### 4. Create a Library for Our tRPC Client

We'll use the same approach and the same `@nrwl/js` library generator for this lib as well: 

```typescript
import { libraryGenerator as jsLibGenerator } from '@nrwl/js';

// ...

async function createTrpcClientLibrary(
  tree: Tree,
  options: AppGeneratorSchema,
  trpcClientName: string
) {
  await jsLibGenerator(tree, {
    name: trpcClientName,
    bundler: 'vite',
    unitTestRunner: 'none',
  });
  createTrpcClientBoilerPlate(tree, options.name);
  tree.delete(`libs/${trpcClientName}/src/lib/${trpcClientName}.ts`);
  updateJson(tree, `libs/${trpcClientName}/project.json`, (json) => ({
    ...json,
    sourceRoot: `libs/${trpcClientName}/src`,
  }));
}

function createTrpcClientBoilerPlate(tree: Tree, name: string) {
  const { className, fileName } = names(name);
  const { npmScope } = getWorkspaceLayout(tree);
  const trpcClientBoilerPlate = `import { ${className}TrpcRouter } from '@${npmScope}/${fileName}-trpc-server';
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';

export const create${className}TrpcClient = () =>
  createTRPCProxyClient<${className}TrpcRouter>({
    links: [httpBatchLink({ url: '/api' })],
  } as any);
`;
  tree.write(
    `libs/${fileName}-trpc-client/src/index.ts`,
    trpcClientBoilerPlate
  );
}
```

The client here is pretty trivial and not expected to change, so we'll use 'none' for our `unitTestRunner` option to avoid adding unit tests for this lib.

### Putting Our Whole Generator Together

With all this in place, we're good to go! To recap, here's the entirety of our function now:

```typescript
import { getWorkspaceLayout, names, Tree, updateJson } from '@nrwl/devkit';
import { applicationGenerator as nodeAppGenerator } from '@nrwl/node';
import { libraryGenerator as jsLibGenerator } from '@nrwl/js';
import {
  applicationGenerator as reactAppGenerator,
  setupTailwindGenerator,
} from '@nrwl/react';
import { AppGeneratorSchema } from './schema';
import { Linter } from '@nrwl/linter';

const defaultPorts = {
  frontendPort: 3000,
  backendPort: 3333,
};

export default async function (tree: Tree, options: AppGeneratorSchema) {
  const optionsWithDefaults = {
    ...defaultPorts,
    ...options,
  };
  const kebobCaseName = names(optionsWithDefaults.name).fileName;
  const webAppName = `${kebobCaseName}-web`;
  const serverName = `${kebobCaseName}-server`;
  const trpcServerName = `${kebobCaseName}-trpc-server`;
  const trpcClientName = `${kebobCaseName}-trpc-client`;
  await createReactApplication(tree, optionsWithDefaults, webAppName);
  await createNodeApplication(
    tree,
    optionsWithDefaults,
    serverName,
    webAppName
  );
  await createTrpcServerLibrary(tree, optionsWithDefaults, trpcServerName);
  await createTrpcClientLibrary(tree, optionsWithDefaults, trpcClientName);
}

// omitting the function implementations, but they would go below here
```

Which reads like a list of exactly what we set out to do at the start!

> The generators in your Local Plugin can serve as the expected scaffolding for our stack codified (as well as automated!). 
> 
> This way, if you decide to alter your stack or change this scaffolding - you can adjust your generators via a Pull Request.
> 
> That pull request can serve as a discussion place to verify the change, and once merged, your stack will follow the new standard!

## Use your Generator <a name="using-a-generator"></a>

To use the generator now, run the command:

```
npx nx generate @nx-trpc-example/plugin:app test
```

Where `@nx-trpc-example` is the name of our workspace, `plugin` is the name of the plugin we created, `app` is the name of the generator we created, and `test` is the name of the full-stack app we want to create.

After creating our `test` app via the generator, we can run the command to start our server:

```
npx nx serve test-server
```

And in a separate terminal, we can run the command:

```
npx nx serve test-web
```

To run our react application that communicates with our server. This should give us our full-stack development environment to respond to our saves as long as these `serve` commands are running. But wouldn't it be great to run this in the same terminal via a single command?

In the next section, we'll create an executor that will allow us to run both of these serves in a single command.

## Create an Executor <a name="create-executor"></a>

Next, we'll add a way to run both the frontend and backend applications in a development mode in a single command. As it turns out, Nx gives us a way out of the box to do this via:

```
% npx nx run-many --target=serve

 >  NX   Running target serve for 2 projects:

    - test-server
    - test-web

 —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

> nx run test-server:serve


> nx run test-web:serve:development

Loading proxy configuration from: /Users/zackderose/nx-recipes/trpc-example-stack/apps/test-web/proxy.conf.json
  ➜  Local:   http://127.0.0.1:3000/
Debugger listening on ws://localhost:9229/7c8bd276-1df5-43e2-81cc-7de68cde56c4
Debugger listening on ws://localhost:9229/7c8bd276-1df5-43e2-81cc-7de68cde56c4
For help, see: https://nodejs.org/en/docs/inspector
Listening at http://localhost:3333/api
[ watch ] build succeeded, watching for changes...
```

With that in place, both our client and server are started with the one command, but it's difficult to tell in the terminal which line belongs to which process, so we'll address this by creating an executor so that our resulting terminal looks like this:

![Expected Terminal](https://hackmd.io/_uploads/By1y-c1hj.png)

(That's everything for the high-level view of understanding of executors! Feel free to [skip ahead to how we can use this executor once it's created](#using-an-executor), or other stick around for an in-depth explanation of the implementation details!)

Our goal in Nx's task-running API is to simplify any task to a single function: an executor. When we use Nx to run a task (like with the command: `npx nx build my-app`) Nx will determine the executor function attached to `my-app`'s `build` target and call that executor function!

To create an executor that will serve both the front and backend applications, we'll start by running the command:

```
npx nx g @nrwl/nx-plugin:executor serve-fullstack --project=plugin
```

This will run [the `executor` generator of the `@nrwl/nx-plugin` package](/packages/nx-plugin/generators/executor) to create an executor called `serve-fullstack` in our `plugin` library, with all the scaffolding managed for us.

Not unlike our generator, we can adjust the parameterization of the executor here, starting with the `libs/plugin/src/executors/serve-full-stack/schema.d.ts`:

```typescript
export interface ServeFullstackExecutorSchema {
  frontendProject: string;
  backendProject: string;
}
```

This will require us to provide the name of the `frontendProject` and `backendProject` in the options of any targets we set up for this executor. We'll also add the corresponding `libs/plugin/src/executors/serve-full-stack/schema.json` file:

```json
{
  "$schema": "http://json-schema.org/schema",
  "version": 2,
  "cli": "nx",
  "title": "ServeFullstack executor",
  "description": "",
  "type": "object",
  "properties": {
    "frontendProject": {
      "type": "string",
      "description": "The name of the frontend project to serve."
    },
    "backendProject": {
      "type": "string",
      "description": "The name of the backend project to serve."
    }
  },
  "required": ["frontendProject", "backendProject"]
}
```

With that setup, we'll start in the `libs/plugin/src/executors/serve-fullstack/executor.ts` file.

> While we could take a similar approach as our generators and import the executors from our `@nrwl/...` packages, this is not as feasible for our use case.
> 
> Long-running executors (a task that is expected to keep running until canceled, for instance: a `serve` - as opposed to executors that complete at some point, like a `build`) return an `AsyncInterator` from the existing first-party Nx Executors currently, which are a bit more challenging to work with.


So instead, we'll use Node's `child_process` api to use Nx's CLI to call the `serve`'s of our web app and server. Then we'll listen to emissions from these processes' `stdio` and `stderr` and log them with a good-looking prefix. In the end, it should look like this:

### Starting our Server

We'll start by running the server first, as we'll need our server running for our web server's proxy to be created correctly when we start running it.

Here's the code to do so:

{% embed https://gist.github.com/ZackDeRose/8881e4bec7f127cd5bdd4ab889b19786 %}

The `startBackendServer()` function will return a Promise that never resolves. This will ensure that the task will never complete (this is another way of achieving a long-running task, in addition to the `AsyncIterators` mentioned before).

Line 16 above creates the child process, and lines 19-20 will ensure that if our parent process ends (like if the user pressed `CTRL-C` while it's running), the child process will be `kill()`ed as well.

On lines 21-25, we'll also listen for the substring: `'build succeeded, watching for changes...'` here to start up our frontend serve next!

> Note the second input here: the [`ExecutorContext`](https://github.com/nrwl/nx/blob/master/packages/nx/src/config/misc-interfaces.ts#L147) on line 9 above. 
> 
> We won't use this in the generator in this example. Still, when Nx calls this function to run our executor, it will provide all of this potentially helpful context information in the second parameter here, which is useful for many other use cases!

### Starting our React App

We'll use the following function to start up our frontend server:

```typescript
async function startFrontendServer(frontendProject: string) {
  return new Promise(() => {
    const childProcess = exec(`npx nx serve ${frontendProject}`, {
      maxBuffer: LARGE_BUFFER,
    });
    process.on('exit', () => childProcess.kill());
    process.on('SIGTERM', () => childProcess.kill());
  });
}
```

This should look very similar to the function we wrote to start our backendProject!

With this function in place, we can adjust our `startBackendServer()` function now to call this only the first the backend server starts correctly:


{% embed https://gist.github.com/ZackDeRose/ee4d29ed1f3386bb2f01d54a7c64c02b %}

Notice we added a variable to track whether we've started the frontend server before on line 2 above, and we added a check in line 10, just before calling our `startFrontendServer()` function on line 11.

### Adding Good-Looking Prefixes to Our logs

At this point, our executor is functional, but if we run it right now, the logs will be empty! We'll fix this by creating a function that, when given a `ChildProcess` and a `prefix`, will relay anything logged by the process to the parent's `stdout`.

Since we want the prefixes to look nice, later, we'll use `chalk` (that Nx already has as a dependency, so it's already installed) and add some logic to center the name of the project and make all prefixes take the same amount of space:

{% embed https://gist.github.com/ZackDeRose/b8d60c07a1fb8cbc00edda2f1eca72ce %}

Lines 2-12 above create a reusable function to prefix every line of any incoming string and send it to the parent process's `stdout` by calling `console.log()`, and lines 13-16 set up hooks to call this function whenever the given `ChildProcess` writes to `stdout` or `stderr`.

The function to `padTargetName` on lines 19-25 above will ensure that our prefix is centered and the same length as any other project name.

All that's left is to put it all together now:

{% embed https://gist.github.com/ZackDeRose/a1e61eaa806b9104cdcc10f81accd020 %}

Notice lines 12-13 above determines the targeted prefix width, and we adjusted our `startBackendServer()` and our `startFrontendServer()` functions to accept this as a parameter as well, and lines 28-31 and 54-57 add our `prefixTerminalOutput()` so both the `ChildProcess`es are prefixed correctly.

## Use your Executor <a name="using-an-executor"></a>

To use our executor, we'll start by adding a new target to our `apps/test-web/project.json file`:

{% embed https://gist.github.com/ZackDeRose/ee7ffdc28fb9ae9aa611e81d5a65907c %}

Lines 8-14 above add a new `serve-fullstack` target to our `test-web` app that we already created with our generator. Line 9 tells it to use the executor we just wrote, and lines 10-13 give that executor the required parameters we set up for it.

All that's left to do now is call this target from the Nx CLI:

```bash
npx nx serve-fullstack test-web
```

![expected output](https://hackmd.io/_uploads/By1y-c1hj.png)


## Putting it All Together <a name="putting-it-all-together"></a>

As a bonus step, now that we have our executor set up, we can automate this step of adding the `serve-fullstack` target to the right `project.json` file.

Going back to `libs/plugin/src/generators/app/generator.ts`, and specifically the `createReactApplication()` function, we can add another function called `addFullStackServeTarget` and call it after generating the rest of our react application:

{% embed https://gist.github.com/ZackDeRose/7f035cf26201e167237edcf821895317 %}

Starting at line 26, we use [`@nrwl/devkit`'s `updateJson()` function](https://nx.dev/packages/devkit/documents/index#updatejson) to do just this to the `project.json` of the react app that we'll create in this generator.

As we can see in the result, the whole picture of what we've created allows us to "stamp out" any number of full stack applications to add to our monorepo's workspace, given simply a name (and optionally a frontend or backend port!). We are then fully set up to execute a full-stack serve where we can easily distinguish the logs from our frontend vs. backend serve processes, as we saw in our original teaser:

{%youtube 2YwD6HRFI1I %}

## Recap

In this article, we looked at the history and evolution of tech stacks and saw how Nx is positioned to be an integral part of creating and maintaining tech stacks in the future!

While this plugin currently exists only within our current workspace that we built together, we'll follow up this blog post with another on how to publish and share your plugin with the broader community (including an `init` generator for installing required packages, and a `preset` generator for creating a new workspace from scratch!)

## Learn more
+ [🧠 Nx Docs](https://nx.dev/)
+ [👩‍💻 Nx GitHub](https://github.com/nrwl/nx)
+ [💬 Nrwl Community Slack](https://go.nrwl.io/join-slack)
+ [📹 Nrwl Youtube Channel](https://www.youtube.com/@nxdevtools)
+ [🧐 Need help with Angular, React, Monorepos, Lerna, or Nx? Talk to us 😃](https://nrwl.io/contact-us)

If you liked this, click the ❤️ and make sure to follow [Zack](https://twitter.com/zackderose) and [Nx](https://twitter.com/NxDevTools) on Twitter for more!