Hasan Iqbal
Hasan Iqbal

Reputation: 149

How to deploy both a Next.js app and an Express.js API from a monorepo?

I have a Next.js (pages) app, with many API routes in the /api folder. I am hosting this on Vercel, where there is a 10 second execution limit.

I have some API routes that take a long time to process data, and I want to move these to a standalone serverfull Express.js application, so they can take as long as they need.

I'm wondering how I can have both the Express.js server and the Next.js application in a monorepo? And then how do I host the Next.js portion of the monorepo on Vercel, and the Express.js portion on Railway? I want to have shared types and interfaces between the Next.js app and the Express.js app (e.g., re-use some util functions in the express app).

Currently, I have a completely seperate Express.js server where I've essentially re-written many of the types, interfaces and functions that could otherwise be shared if it was in a mono-repo.

I have tried to use nx, but my issue is that I already have the Next.js application, and I'm not sure how to integrate that into a monorepo, and then generate an express application using the nx CLI.

Upvotes: 1

Views: 981

Answers (1)

Hasan Iqbal
Hasan Iqbal

Reputation: 149

I eventually figured out how to achieve this, so I will share my answer:

To create a Next.js and Express monorepo, with shared packages:

  1. Create an integrated monorepo using npx create-nx-workspace@latest. Give this monorepo a workspace name, for example 'Google' would be the workspace name and within it you'd have 'Gmail', 'Google Images', etc.
  2. Select react stack and then Next.js
  3. Select the integrated monorepo option.
  4. Name the Next.js app, something like nextjs-app or frontend or web.
  5. Open the Nx workspace by doing cd <workspace name> && code .
  6. Run nx add @nx/express inside the workspace root directory to get node generators
  7. Make sure you have the Nx VSCode extension installed.
  8. Go to the Nx extension --> generate --> node application.
  9. Name the app (e.g., api), set the directory to apps/{name} (e.g., apps/api), select express as the framework, select the Next.js app as the frontendProject, click Generate button to automatically create this app. You should now see at least 2 projects in the projects tab within the Nx extension (probably 4 items, if there's an e2e test for each repo).
  10. Commit everything, and push it to a GitHub repository.
  11. From Vercel, to host the Next.js app, select the GitHub repository, but override the build command and set it to: npx nx run {next.js app's name}:build --prod so e.g., npx nx run nextjs-app:build --prod, and select the output directory to ./apps/nextjs-app/.next.
  12. I hosted my Express server in Railway, so from Railway, again select the GitHub repo, and set the custom build command to npx nx run express-api:build:production and set the custom start command to nx reset && npx nx run express-api:serve --configuration=production

Now you have one connected repository with the backend express API and frontend Next.js app hosted in different places.

To create shared types (or whatever else) that can be used in both projects:

  1. Open the Nx extension in VSCode
  2. Generate --> @nx/node - library
  3. Name the library shared-types, set the directory to apps/shared-types, expand show more options, set the import path to @{your project name}/shared-types.
  4. Click Generate

Now, inside the apps/ folder, you'll see the shared-types folder. If you go into apps/shared-types/src/lib/shared-types.ts, you'll see it exports the following function:

export function sharedTypes(): string {
  return 'shared-types';
}

To test out the monorepo, you can open the express app in apps/express-api/src/main.ts, and do this:

import { sharedTypes } from '@stack-overflow/shared-types';
import express from 'express';

const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ? Number(process.env.PORT) : 3000;

const shared = sharedTypes();

const app = express();

app.get('/', (req, res) => {
  res.send({ message: shared });
});

app.listen(port, host, () => {
  console.log(`[ ready ] http://${host}:${port}`);
});

Now, if you run the API (use the Nx extension it's easier), you'll see the following response to a GET request made to /:

{
  "message": "shared-types"
}

And when interfacing with the express server from the next.js app, just have something like "EXTERNAL_API" as an .env variable, and just use process.env.EXTERNAL_API. On dev, set it to localhost and on prod set it to whatever Railway gives you.

For webhooks, use Ngrok and just set up a proxy to the port on your local machine that's running the express server. So if the express server is running on :4000, just run ngrok http 4000, copy that URL and on dev just set process.env.EXTERNAL_API on the nextjs app to the URL Ngrok gives you.

Upvotes: 2

Related Questions