Files
create-better-t-stack/apps/cli/templates/backend/server/fastify/src/index.ts.hbs
2025-08-10 20:19:55 +05:30

152 lines
3.8 KiB
Handlebars

import "dotenv/config";
import Fastify from "fastify";
import fastifyCors from "@fastify/cors";
{{#if (eq api "trpc")}}
import { fastifyTRPCPlugin, type FastifyTRPCPluginOptions } from "@trpc/server/adapters/fastify";
import { createContext } from "./lib/context";
import { appRouter, type AppRouter } from "./routers/index";
{{/if}}
{{#if (eq api "orpc")}}
import { RPCHandler } from "@orpc/server/node";
import { CORSPlugin } from "@orpc/server/plugins";
import { appRouter } from "./routers/index";
import { createServer } from "node:http";
{{#if auth}}
import { createContext } from "./lib/context";
{{/if}}
{{/if}}
{{#if (includes examples "ai")}}
import { streamText, type UIMessage, convertToModelMessages } from "ai";
import { google } from "@ai-sdk/google";
{{/if}}
{{#if auth}}
import { auth } from "./lib/auth";
{{/if}}
const baseCorsConfig = {
origin: process.env.CORS_ORIGIN || "",
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: [
"Content-Type",
"Authorization",
"X-Requested-With"
],
credentials: true,
maxAge: 86400,
};
{{#if (eq api "orpc")}}
const handler = new RPCHandler(appRouter, {
plugins: [
new CORSPlugin({
origin: process.env.CORS_ORIGIN,
credentials: true,
allowHeaders: ["Content-Type", "Authorization"],
}),
],
});
const fastify = Fastify({
logger: true,
serverFactory: (fastifyHandler) => {
const server = createServer(async (req, res) => {
const { matched } = await handler.handle(req, res, {
context: await createContext(req.headers),
prefix: "/rpc",
});
if (matched) {
return;
}
fastifyHandler(req, res);
});
return server;
},
});
{{else}}
const fastify = Fastify({
logger: true,
});
{{/if}}
fastify.register(fastifyCors, baseCorsConfig);
{{#if auth}}
fastify.route({
method: ["GET", "POST"],
url: "/api/auth/*",
async handler(request, reply) {
try {
const url = new URL(request.url, `http://${request.headers.host}`);
const headers = new Headers();
Object.entries(request.headers).forEach(([key, value]) => {
if (value) headers.append(key, value.toString());
});
const req = new Request(url.toString(), {
method: request.method,
headers,
body: request.body ? JSON.stringify(request.body) : undefined,
});
const response = await auth.handler(req);
reply.status(response.status);
response.headers.forEach((value, key) => reply.header(key, value));
reply.send(response.body ? await response.text() : null);
} catch (error) {
fastify.log.error({ err: error }, "Authentication Error:");
reply.status(500).send({
error: "Internal authentication error",
code: "AUTH_FAILURE"
});
}
}
});
{{/if}}
{{#if (eq api "trpc")}}
fastify.register(fastifyTRPCPlugin, {
prefix: "/trpc",
trpcOptions: {
router: appRouter,
createContext,
onError({ path, error }) {
console.error(`Error in tRPC handler on path '${path}':`, error);
},
} satisfies FastifyTRPCPluginOptions<AppRouter>["trpcOptions"],
});
{{/if}}
{{#if (includes examples "ai")}}
interface AiRequestBody {
id?: string;
messages: UIMessage[];
}
fastify.post('/ai', async function (request, reply) {
// there are some issues with the ai sdk and fastify, docs: https://ai-sdk.dev/cookbook/api-servers/fastify
const { messages } = request.body as AiRequestBody;
const result = streamText({
model: google('gemini-1.5-flash'),
messages: convertToModelMessages(messages),
});
return result.pipeUIMessageStreamToResponse(reply.raw);
});
{{/if}}
fastify.get('/', async () => {
return 'OK';
});
fastify.listen({ port: 3000 }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log("Server running on port 3000");
});