Standalone API
This guide will show you how to deploy your saasbrella API as a dedicated service. This can be useful if you want to run your API and frontend on different platforms or if you want youc API to be a “long-running” service instead of a serverless deployment. You probably want to use this if you have tasks in your backend that have a long execution duration (that would exceed the timeout limit of a serverless deployment for example).
We have a fully working example of this setup in the saasbrella-repository on the api-deployment branch.
You can either checkout this branch directly or follow the steps below to set it up yourself.
Create a dedicated app for your API
First you want to create a dedciated app inside your monorepo. This will be the app that will run the API as a node.js server.
Create the following files inside your monorepo:
{ "dependencies": { "@hono/node-server": "^1.13.7", "@repo/api": "workspace:*", "@repo/logs": "workspace:*" }, "devDependencies": { "@repo/tsconfig": "workspace:*", "@types/node": "22.10.3", "@types/react": "19.0.2", "@types/react-dom": "19.0.2", "esbuild": "^0.24.2", "tsx": "^4.19.2", "typescript": "5.7.2" }, "name": "@repo/api-app", "private": true, "scripts": { "build": "esbuild ./src/index.ts --bundle --platform=node --outfile=dist/index.js", "dev": "tsx --env-file=../../.env.local src/index.ts --watch", "start": "node dist/index.js", "type-check": "tsc --noEmit" }, "version": "0.0.0"}{ "extends": "@repo/tsconfig/react-library.json", "include": ["src/*.ts"], "exclude": ["node_modules"]}import { serve } from "@hono/node-server";import { app } from "@repo/api";import { logger } from "@repo/logs";
const port = Number.parseInt(process.env.PORT || "3001");
serve( { fetch: app.fetch, port, }, () => { logger.info(`Server is running on port ${port}`); },);Add rewrite rule to web app
The API will be running on a different URL than the app. To not have to handle cross-origin requests, we are going to proxy the API requests through the web app. To do this, you need to add a rewrite rule to your web app:
const nextConfig: NextConfig = { // ... async rewrites() { return [ { source: "/api/:path((?!docs-search$).*)", destination: `${process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3001"}/api/:path*`, }, ]; },};Note: We are redirecting all requests to the /api path to the API service, except for the /api/docs-search path. This is because the docs search is a serverless function inside our web app and we don’t want to proxy it through the API service.
Now you should be able to run the API service locally, by running the known pnpm dev command inside your monorepo. It will start both apps in development mode.
To keep the repository clean, you can also remove the /apps/web/app/api/[[...rest]] folder, as it is no longer needed.
Deploy API service
How to deploy the API service depends on where you want to run it. You can wrap the API service in a docker container and deploy it on any platform that supports Docker containers.
Deploy as Node.js server
To run the API service as a Node.js server, all you need to do is to connect your repository, build the API service and start the server.
Depending on the platform the commands for this will be different. For example, if you want to deploy on a platform that supports Docker, you can use the following commands:
# Build the API servicecorepack enable; pnpm install --frozen-lockfile; pnpm --filter @repo/api-app build
# Start the API servicepnpm --filter @repo/api-app startDeploy as Docker container
To run the API service as a Docker container, you need to create a Dockerfile and build the container.
Add the following Dockerfile to your apps/api folder:
FROM node:22-alpine AS baseENV PNPM_HOME="/pnpm"ENV PATH="$PNPM_HOME:$PATH"RUN corepack enable
FROM base AS builderRUN apk add --no-cache libc6-compatRUN apk updateWORKDIR /appRUN pnpm add -g turboCOPY . .RUN turbo prune @repo/api-app --docker
FROM base AS installerRUN apk add --no-cache libc6-compatRUN apk updateWORKDIR /app
COPY .gitignore .gitignoreCOPY --from=builder /app/out/json/ .COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install
COPY --from=builder /app/out/full/ .COPY turbo.json turbo.json
RUN pnpm turbo run build --filter=@repo/api-app...
FROM base AS runnerWORKDIR /app
RUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 apiUSER api
COPY --from=installer /app/apps/api/dist/index.js .
USER api
EXPOSE 3001
ENV PORT=3001ENV HOSTNAME="0.0.0.0"
CMD ["node", "/app/index.js"]To make Prisma work in the Docker container, you need to set the engine type to binary in the schema.prisma file:
generator client { provider = "prisma-client-js" engineType = "binary" // [!code ++]}Now you deploy the API service to your platform of choice.
Set the API url in the web app
Finally, you need to provide the API url to the web app.
You can do so by defining the NEXT_PUBLIC_API_URL environment variable in the deployment environment of the web app.
Dending on your platform, you might have different deployments for different branches.
A common use case is to have a production deployment for the main branch and a staging deployment for the staging or dev branch.
In this case you can define different environment variables for each deployment, to work with the correct API for each environment.