Feature: Fastify Implementation (#274)

Co-authored-by: Aman Varshney <amanvarshney.work@gmail.com>
This commit is contained in:
Niloy
2025-05-22 17:32:32 +02:00
committed by GitHub
parent 17a7386071
commit b9dae19ce5
14 changed files with 243 additions and 2 deletions

View File

@@ -74,6 +74,9 @@ export const dependencyVersionMap = {
"@types/express": "^5.0.1",
"@types/cors": "^2.8.17",
fastify: "^5.3.3",
"@fastify/cors": "^11.0.1",
turbo: "^2.4.2",
ai: "^4.3.16",

View File

@@ -42,6 +42,12 @@ export async function setupBackendDependencies(
dependencies.push("express", "cors");
devDependencies.push("@types/express", "@types/cors");
if (runtime === "node") {
devDependencies.push("tsx", "@types/node");
}
} else if (framework === "fastify") {
dependencies.push("fastify", "@fastify/cors");
if (runtime === "node") {
devDependencies.push("tsx", "@types/node");
}

View File

@@ -268,6 +268,8 @@ function generateFeaturesList(
addonsList.push("- **Hono** - Lightweight, performant server framework");
} else if (backend === "express") {
addonsList.push("- **Express** - Fast, unopinionated web framework");
} else if (backend === "fastify") {
addonsList.push("- **Fastify** - Fast, low-overhead web framework");
} else if (backend === "elysia") {
addonsList.push("- **Elysia** - Type-safe, high-performance framework");
} else if (backend === "next") {

View File

@@ -140,7 +140,15 @@ async function main() {
.option("backend", {
type: "string",
describe: "Backend framework",
choices: ["hono", "express", "next", "elysia", "convex", "none"],
choices: [
"hono",
"express",
"fastify",
"next",
"elysia",
"convex",
"none",
],
})
.option("runtime", {
type: "string",

View File

@@ -33,6 +33,11 @@ export async function getBackendFrameworkChoice(
label: "Express",
hint: "Fast, unopinionated, minimalist web framework for Node.js",
},
{
value: "fastify" as const,
label: "Fastify",
hint: "Fast, low-overhead web framework for Node.js",
},
{
value: "elysia" as const,
label: "Elysia",

View File

@@ -17,6 +17,7 @@ export type ProjectAddons =
export type ProjectBackend =
| "hono"
| "express"
| "fastify"
| "next"
| "elysia"
| "convex"

View File

@@ -91,6 +91,29 @@ export async function createContext(opts: any) {
{{/if}}
}
{{else if (eq backend 'fastify')}}
import type { IncomingHttpHeaders } from "node:http";
{{#if auth}}
import { fromNodeHeaders } from "better-auth/node";
import { auth } from "./auth";
{{/if}}
export async function createContext(req: IncomingHttpHeaders) {
{{#if auth}}
const session = await auth.api.getSession({
headers: fromNodeHeaders(req),
});
return {
session,
};
{{else}}
// No auth configured
return {
session: null,
};
{{/if}}
}
{{else}}
export async function createContext() {
return {

View File

@@ -95,6 +95,27 @@ export async function createContext(opts: CreateExpressContextOptions) {
{{/if}}
}
{{else if (eq backend 'fastify')}}
import type { CreateFastifyContextOptions } from "@trpc/server/adapters/fastify";
{{#if auth}}
import { fromNodeHeaders } from "better-auth/node";
import { auth } from "./auth";
{{/if}}
export async function createContext({ req, res }: CreateFastifyContextOptions) {
{{#if auth}}
const session = await auth.api.getSession({
headers: fromNodeHeaders(req.headers),
});
return { session };
{{else}}
// No auth configured
return {
session: null,
};
{{/if}}
}
{{else}}
export async function createContext() {
return {

View File

@@ -0,0 +1,155 @@
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 type { FastifyRequest, FastifyReply } from "fastify";
import { streamText, type Message } 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("Authentication Error:", 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: Message[];
}
fastify.post('/ai', async function (request, reply) {
const { messages } = request.body as AiRequestBody;
const result = streamText({
model: google('gemini-1.5-flash'),
messages,
});
reply.header('X-Vercel-AI-Data-Stream', 'v1');
reply.header('Content-Type', 'text/plain; charset=utf-8');
return reply.send(result.toDataStream());
});
{{/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");
});