Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions monorepo-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
node_modules
.turbo
*.log
.next
dist
dist-ssr
*.local
.env
.cache
server/dist
public/dist
*.db
1 change: 1 addition & 0 deletions monorepo-example/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
link-workspace-packages=true
21 changes: 21 additions & 0 deletions monorepo-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Better Auth Monorepo Example

This is an example of how to use Better Auth inside a monorepo.

**Implements the following features:**

- Email & Password
- [Fastify auth server](apps/api)
- [NextJS app](apps/web)
- [SolidStart app](apps/dashboard)

## How to run

1. Clone the code sandbox (or the repo) and open it in your code editor
2. Rename all .env.example files to .env and provide necessary variables
3. Run the following commands
```bash
pnpm install
pnpm apps:dev
```
4. Open the browser and navigate to `http://localhost:3000/sign-up`
1 change: 1 addition & 0 deletions monorepo-example/apps/api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CLIENT_ORIGIN="http://localhost:3000,http://localhost:3001"
23 changes: 23 additions & 0 deletions monorepo-example/apps/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@monorepo-example/api",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"build": "tsdown",
"start": "node dist/index.js",
"dev": "tsdown --watch --on-success \"node dist/index.js\""
},
"dependencies": {
"@fastify/cors": "^11.1.0",
"@monorepo-example/auth": "workspace:*",
"dotenv": "^17.2.3",
"fastify": "^5.6.1"
},
"devDependencies": {
"@monorepo-example/typescript-config": "workspace:*",
"@types/node": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:"
}
}
69 changes: 69 additions & 0 deletions monorepo-example/apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import "dotenv/config";
import Fastify from "fastify";
import fastifyCors from "@fastify/cors";
import { auth } from "@monorepo-example/auth";

const fastify = Fastify({ logger: true });

// Configure CORS policies
fastify.register(fastifyCors, {
origin: process.env.CLIENT_ORIGIN ? process.env.CLIENT_ORIGIN.split(",") : ["http://localhost:3000", "http://localhost:3001"],
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: [
"Content-Type",
"Authorization",
"X-Requested-With"
],
credentials: true,
maxAge: 86400
});

// Mount authentication handler after CORS registration
// Register authentication endpoint
fastify.route({
method: ["GET", "POST"],
url: `${
auth.options.basePath
? auth.options.basePath.endsWith("/")
? auth.options.basePath
: `${auth.options.basePath}`
: "/api/auth/"
}*`,
async handler(request, reply) {
try {
// Construct request URL
const url = new URL(request.url, `http://${request.headers.host}`);
// Convert Fastify headers to standard Headers object
const headers = new Headers();
Object.entries(request.headers).forEach(([key, value]) => {
if (value) headers.append(key, value.toString());
});
// Create Fetch API-compatible request
const req = new Request(url.toString(), {
method: request.method,
headers,
body: request.body ? JSON.stringify(request.body) : undefined,
});
// Process authentication request
const response = await auth.handler(req);
// Forward response to client
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(error, "Authentication Error");
reply.status(500).send({
error: "Internal authentication error",
code: "AUTH_FAILURE",
});
}
},
});

fastify.listen({ port: 4000 }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log("Server running on port 4000");
});
16 changes: 16 additions & 0 deletions monorepo-example/apps/api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"exclude": ["node_modules"],
"extends": "@monorepo-example/typescript-config/base.json",
"compilerOptions": {
"declarationMap": false,
"declaration": false,
"lib": ["es2021"],
"module": "node16",
"target": "es2021",

"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "node16"
},
}
9 changes: 9 additions & 0 deletions monorepo-example/apps/api/tsdown.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "tsdown";

export default defineConfig([
{
entry: ["./src/index.ts"],
platform: "node",
dts: false,
}
]);
12 changes: 12 additions & 0 deletions monorepo-example/apps/api/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": [
"//"
],
"tasks": {
"build": {
"outputs": [
"dist/**"
]
}
}
}
2 changes: 2 additions & 0 deletions monorepo-example/apps/dashboard/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_API_URL="http://localhost:4000"
VITE_WEB_URL="http://localhost:3000"
28 changes: 28 additions & 0 deletions monorepo-example/apps/dashboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
dist
.wrangler
.output
.vercel
.netlify
.vinxi
app.config.timestamp_*.js

# Environment
.env
.env*.local

# dependencies
/node_modules

# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/

# Temp
gitignore

# System Files
.DS_Store
Thumbs.db
9 changes: 9 additions & 0 deletions monorepo-example/apps/dashboard/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "@solidjs/start/config";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
vite: {
plugins: [tailwindcss() as any],
},
middleware: "src/middleware/index.ts",
});
28 changes: 28 additions & 0 deletions monorepo-example/apps/dashboard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@monorepo-example/dashboard",
"type": "module",
"scripts": {
"dev": "vinxi dev --port 3001",
"build": "vinxi build",
"start": "vinxi start"
},
"dependencies": {
"@kobalte/core": "^0.13.11",
"@monorepo-example/auth": "workspace:*",
"@monorepo-example/shared": "workspace:*",
"@solidjs/router": "^0.15.0",
"@solidjs/start": "^1.1.0",
"class-variance-authority": "catalog:",
"solid-js": "^1.9.5",
"tw-animate-css": "catalog:",
"vinxi": "^0.5.7"
},
"devDependencies": {
"@tailwindcss/vite": "catalog:tailwind",
"@types/node": "catalog:",
"tailwindcss": "catalog:tailwind"
},
"engines": {
"node": ">=22"
}
}
Binary file added monorepo-example/apps/dashboard/public/favicon.ico
Binary file not shown.
123 changes: 123 additions & 0 deletions monorepo-example/apps/dashboard/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}

.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}

@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
19 changes: 19 additions & 0 deletions monorepo-example/apps/dashboard/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";


export default function App() {
return (
<Router
root={props => (
<>
<Suspense>{props.children}</Suspense>
</>
)}
>
<FileRoutes />
</Router>
);
}
Loading