Backend Architecture
Relevant source files
The following files were used as context for generating this wiki page:
The backend architecture of LobeChat is built upon a modern, serverless stack leveraging Next.js API Routes and tRPC for type-safe API communication. It is designed to be scalable and tightly integrated with the Next.js frontend. Authentication is managed at the edge using Clerk middleware, which protects API endpoints and application routes.
The core of the backend is exposed via a single tRPC endpoint, which routes requests to various procedures for handling business logic. This architecture separates concerns between request handling, authentication, and the actual API implementation, providing a robust and maintainable structure.
High-Level Overview
The backend system processes incoming client requests through a series of layers, starting from the edge middleware for authentication, followed by the Next.js API route handler, which then passes the request to the tRPC router for final processing.
The following diagram illustrates the overall backend architecture and request flow.
Authentication and Middleware
Authentication is the first layer of the backend, handled by a Next.js middleware function. This ensures that unauthorized access is prevented before requests even reach the application's core logic.
Sources: src/middleware.ts, CLERK_AUTH_FIX_SUMMARY.md, UNAUTHORIZED_ERROR_INDEX_VI.md
Clerk Middleware
The project uses clerkMiddleware to protect routes. The middleware is configured in src/middleware.ts to define which routes are public and which require authentication.
| Configuration | Description |
|---|---|
publicRoutes | An array of route patterns that are accessible without authentication. This includes API routes for webhooks and authentication callbacks. |
ignoredRoutes | Routes that the middleware will completely skip, such as static assets. |
Sources: src/middleware.ts:10-21, CLERK_AUTH_FIX_SUMMARY.md
Request Flow
The middleware intercepts every incoming request and performs an authentication check using Clerk.
This diagram shows the decision-making process within the clerkMiddleware.
Sources: src/middleware.ts, CLERK_AUTH_FIX_SUMMARY.md
A critical aspect of the middleware implementation is that it retrieves authentication data before creating a response, which was a fix to a previous issue where new users were incorrectly redirected.
Sources: CLERK_AUTH_FIX_SUMMARY.md
API Layer (tRPC)
The application uses tRPC to create a type-safe API layer between the frontend and backend. This eliminates the need for manual type synchronization and provides autocompletion and compile-time safety.
API Entry Point
All tRPC requests are handled by a single Next.js dynamic API route located at src/app/(backend)/trpc/lambda/[trpc]/route.ts. This file acts as the gateway for all backend communication.
It uses fetchRequestHandler from tRPC's server adapters to process incoming HTTP requests and forward them to the main tRPC router, lambdaRouter.
// src/app/(backend)/trpc/lambda/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
// ...
const handler = (req: Request) =>
fetchRequestHandler({
createContext: () => createContext(req),
endpoint: '/trpc/lambda',
onError:
process.env.NODE_ENV === 'development'
? ({ error, path }) => {
console.error(`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message}`);
}
: undefined,
req,
router: lambdaRouter,
});
Sources: src/app/(backend)/trpc/lambda/[trpc]/route.ts:10-25
tRPC Initialization and Procedures
The tRPC instance and different types of procedures are defined in src/libs/trpc/lambda/init.ts. This file is central to the backend's structure.
- Initialization: It creates a tRPC instance using
initTRPC. - Procedures: It defines two key types of procedures:
publicProcedure: For endpoints that do not require authentication.privateProcedure: For endpoints that are protected and require a valid user session.
The privateProcedure is built by attaching a userAuth middleware, which checks for the presence of a userId in the tRPC context. If the userId is missing, it throws an UNAUTHORIZED error.
// src/libs/trpc/lambda/init.ts
import { initTRPC } from '@trpc/server';
// ... other imports
const t = initTRPC.context<Context>().create();
export const router = t.router;
export const publicProcedure = t.procedure;
// This middleware is mentioned in the markdown files as the mechanism for auth.
const userAuth = t.middleware(async ({ ctx, next }) => {
if (!ctx.userId) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next({ ctx: { ...ctx, userId: ctx.userId } });
});
export const privateProcedure = t.procedure.use(userAuth);
Sources: src/libs/trpc/lambda/init.ts:3-17, UNAUTHORIZED_ERROR_INDEX_VI.md
tRPC Router
The main router, lambdaRouter, is defined in src/server/routers/lambda/index.ts. It combines all the individual procedure definitions into a single, unified router that the API entry point can use.
The file includes a healthcheck procedure as an example of a public endpoint.
// src/server/routers/lambda/index.ts
import { publicProcedure, router } from '@/libs/trpc/lambda/init';
export const lambdaRouter = router({
healthcheck: publicProcedure.query(() => 'OK'),
});
export type LambdaRouter = typeof lambdaRouter;
Sources: src/server/routers/lambda/index.ts:3-9
tRPC Request Lifecycle
The following sequence diagram illustrates the complete lifecycle of a private tRPC request from the client to the backend and back.
Conclusion
The backend architecture of LobeChat is a well-structured system that leverages the strengths of the Next.js ecosystem. By using Clerk at the middleware layer, it provides robust, application-wide authentication. The adoption of tRPC offers a highly efficient and type-safe method for building APIs, which are exposed through a single serverless function endpoint. This design is scalable, maintainable, and provides an excellent developer experience by ensuring type safety from the database to the client.