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:
5
.changeset/light-cats-find.md
Normal file
5
.changeset/light-cats-find.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
add fastify
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,3 +35,4 @@ yarn-error.log*
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
.vscode
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
@@ -12,7 +12,7 @@ export default function Features() {
|
||||
<Card
|
||||
icon={<Server />}
|
||||
title="Flexible Backend"
|
||||
description="Choose between Hono, Elysia, Next.js and Express"
|
||||
description="Choose between Hono, Elysia, Next.js, Express, and Fastify"
|
||||
/>
|
||||
<Card
|
||||
icon={<Cable />}
|
||||
|
||||
4
apps/web/public/icon/fastify.svg
Normal file
4
apps/web/public/icon/fastify.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="1226" height="1226" viewBox="0 0 1226 1226" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1226" height="1226" rx="613" fill="black"/>
|
||||
<path d="M1076.59 386.043L1107.75 305.458L1106.4 300.471L798.283 381.572C831.122 334.074 819.831 296 819.831 296C819.831 296 721.45 358.803 647.027 357.118C572.604 355.467 548.646 335.622 434.533 371.976C320.421 408.33 288.201 519.869 255.12 543.841C222.039 567.814 118.254 645.888 118.254 645.888L118.495 647.298L212.057 617.547C212.057 617.547 186.411 641.726 131.885 715.122C131.885 715.122 130.921 714.263 129.338 712.749L129.407 713.265C129.407 713.265 173.227 780.195 216.222 767.779C220.525 766.541 225.413 764.477 230.748 761.76C248.063 771.391 270.645 780.883 295.602 783.463C295.602 783.463 278.7 763.858 264.621 741.537C268.442 739.095 272.332 736.55 276.29 733.97L274.466 735.277L310.094 748.347L306.169 714.882C306.273 714.813 306.41 714.744 306.514 714.675L341.522 727.504L337.15 697.066C341.591 694.761 346.031 692.56 350.438 690.496L386.926 552.543L537.837 449.671L525.858 479.835C495.325 554.985 437.838 572.767 437.838 572.767L413.845 581.847C396.014 602.896 388.51 608.089 382.382 678.803C396.771 675.191 410.506 674.331 422.933 677.668C487.442 695.002 509.748 772.663 492.399 794.159C488.061 799.525 477.7 808.742 464.619 819.507H438.458L438.113 840.694C437.218 841.382 436.323 842.104 435.428 842.792H408.819L408.475 863.463C406.1 865.251 403.793 867.005 401.487 868.725C376.461 869.241 344.792 847.435 344.792 847.435C344.792 867.246 361.315 897.822 361.315 897.822C361.315 897.822 362.382 897.306 364.241 896.412C362.623 897.581 361.728 898.235 361.728 898.235C361.728 898.235 428.716 942.843 470.884 926.334C508.371 911.648 605.444 835.157 689.23 798.974L942.791 732.216L976.216 645.681L782.999 696.55V618.82L1009.71 559.147L1043.13 472.612L783.033 541.09V463.36L1076.59 386.043ZM616.391 550.823L676.562 534.968L677.354 537.96L658.593 586.559L596.219 602.964L616.391 550.823ZM637.182 654.796L574.808 671.202L594.979 619.026L655.151 603.171L655.943 606.163L637.182 654.796ZM718.421 637.358L656.046 653.764L676.218 601.589L736.39 585.733L737.181 588.725L718.421 637.358Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -163,6 +163,13 @@ export const TECH_OPTIONS = {
|
||||
icon: "/icon/express.svg",
|
||||
color: "from-gray-500 to-gray-700",
|
||||
},
|
||||
{
|
||||
id: "fastify",
|
||||
name: "Fastify",
|
||||
description: "Fast, low-overhead web framework for Node.js",
|
||||
icon: "/icon/fastify.svg",
|
||||
color: "from-gray-500 to-gray-700",
|
||||
},
|
||||
{
|
||||
id: "convex",
|
||||
name: "Convex",
|
||||
|
||||
Reference in New Issue
Block a user