mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Feature: Fastify Implementation ⚡ (#274)
Co-authored-by: Aman Varshney <amanvarshney.work@gmail.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -17,6 +17,7 @@ export type ProjectAddons =
|
||||
export type ProjectBackend =
|
||||
| "hono"
|
||||
| "express"
|
||||
| "fastify"
|
||||
| "next"
|
||||
| "elysia"
|
||||
| "convex"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
155
apps/cli/templates/backend/server/fastify/src/index.ts.hbs
Normal file
155
apps/cli/templates/backend/server/fastify/src/index.ts.hbs
Normal 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");
|
||||
});
|
||||
Reference in New Issue
Block a user