├── .gitattributes ├── public └── favicon.ico ├── src ├── lib │ ├── db │ │ ├── schema │ │ │ ├── index.ts │ │ │ └── auth.schema.ts │ │ └── index.ts │ ├── utils.ts │ └── auth │ │ ├── auth-client.ts │ │ ├── queries.ts │ │ ├── functions.ts │ │ ├── middleware.ts │ │ └── auth.ts ├── env │ ├── client.ts │ └── server.ts ├── routes │ ├── api │ │ └── auth │ │ │ └── $.ts │ ├── (authenticated) │ │ ├── route.tsx │ │ └── dashboard │ │ │ ├── index.tsx │ │ │ └── route.tsx │ ├── (auth-pages) │ │ ├── route.tsx │ │ ├── login.tsx │ │ └── signup.tsx │ ├── __root.tsx │ └── index.tsx ├── components │ ├── ui │ │ ├── label.tsx │ │ ├── input.tsx │ │ ├── sonner.tsx │ │ ├── button.tsx │ │ └── dropdown-menu.tsx │ ├── default-not-found.tsx │ ├── sign-out-button.tsx │ ├── sign-in-social-button.tsx │ ├── default-catch-boundary.tsx │ ├── theme-toggle.tsx │ └── theme-provider.tsx ├── router.tsx ├── styles.css └── routeTree.gen.ts ├── .vscode ├── extensions.json └── settings.json ├── .editorconfig ├── .prettierrc ├── .prettierignore ├── .gitignore ├── docker-compose.yml ├── drizzle.config.ts ├── .env.example ├── components.json ├── tsconfig.json ├── vite.config.ts ├── eslint.config.js ├── LICENSE ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnize/react-tanstarter/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/lib/db/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth.schema"; 2 | // export your other schemas here 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "prettier.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "bradlc.vscode-tailwindcss" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | max_line_length = 90 -------------------------------------------------------------------------------- /src/lib/auth/auth-client.ts: -------------------------------------------------------------------------------- 1 | import { createAuthClient } from "better-auth/react"; 2 | import { env } from "~/env/client"; 3 | 4 | const authClient = createAuthClient({ 5 | baseURL: env.VITE_BASE_URL, 6 | }); 7 | 8 | export default authClient; 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "printWidth": 90, 5 | "singleQuote": false, 6 | "endOfLine": "lf", 7 | "trailingComma": "all", 8 | "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"] 9 | } 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # lockfiles 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | bun.lock 6 | 7 | # misc 8 | routeTree.gen.ts 9 | .tanstack/ 10 | drizzle/ 11 | .drizzle/ 12 | 13 | # build outputs 14 | .vercel 15 | .output 16 | .wrangler 17 | .netlify 18 | dist -------------------------------------------------------------------------------- /src/env/client.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-core"; 2 | import * as z from "zod"; 3 | 4 | export const env = createEnv({ 5 | clientPrefix: "VITE_", 6 | client: { 7 | VITE_BASE_URL: z.url().default("http://localhost:3000"), 8 | }, 9 | runtimeEnv: import.meta.env, 10 | }); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Ignore lockfiles we don't use 4 | # package-lock.json 5 | # yarn.lock 6 | # pnpm-lock.yaml 7 | # bun.lock 8 | 9 | .DS_Store 10 | .cache 11 | .env 12 | 13 | .data 14 | .vercel 15 | .output 16 | .wrangler 17 | .netlify 18 | dist 19 | /build/ 20 | /api/ 21 | /server/build 22 | /public/build 23 | 24 | .tanstack -------------------------------------------------------------------------------- /src/lib/auth/queries.ts: -------------------------------------------------------------------------------- 1 | import { queryOptions } from "@tanstack/react-query"; 2 | import { $getUser } from "./functions"; 3 | 4 | export const authQueryOptions = () => 5 | queryOptions({ 6 | queryKey: ["user"], 7 | queryFn: ({ signal }) => $getUser({ signal }), 8 | }); 9 | 10 | export type AuthQueryResult = Awaited>; 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: postgres:alpine 4 | ports: 5 | - 5432:5432 6 | volumes: 7 | - postgres_data_tanstarter:/var/lib/postgresql/data 8 | environment: 9 | - POSTGRES_USER=user 10 | - POSTGRES_PASSWORD=password 11 | - POSTGRES_DB=tanstarter 12 | 13 | volumes: 14 | postgres_data_tanstarter: 15 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "drizzle-kit"; 2 | import { env } from "~/env/server"; 3 | 4 | export default { 5 | out: "./drizzle", 6 | schema: "./src/lib/db/schema/index.ts", 7 | breakpoints: true, 8 | verbose: true, 9 | strict: true, 10 | dialect: "postgresql", 11 | casing: "snake_case", 12 | dbCredentials: { 13 | url: env.DATABASE_URL, 14 | }, 15 | } satisfies Config; 16 | -------------------------------------------------------------------------------- /src/routes/api/auth/$.ts: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { auth } from "~/lib/auth/auth"; 3 | 4 | export const Route = createFileRoute("/api/auth/$")({ 5 | server: { 6 | handlers: { 7 | GET: ({ request }) => { 8 | return auth.handler(request); 9 | }, 10 | POST: ({ request }) => { 11 | return auth.handler(request); 12 | }, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/lib/db/index.ts: -------------------------------------------------------------------------------- 1 | import { createServerOnlyFn } from "@tanstack/react-start"; 2 | import { drizzle } from "drizzle-orm/postgres-js"; 3 | import postgres from "postgres"; 4 | import { env } from "~/env/server"; 5 | 6 | import * as schema from "~/lib/db/schema"; 7 | 8 | const driver = postgres(env.DATABASE_URL); 9 | 10 | const getDatabase = createServerOnlyFn(() => 11 | drizzle({ client: driver, schema, casing: "snake_case" }), 12 | ); 13 | 14 | export const db = getDatabase(); 15 | -------------------------------------------------------------------------------- /src/env/server.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-core"; 2 | import * as z from "zod"; 3 | 4 | export const env = createEnv({ 5 | server: { 6 | DATABASE_URL: z.url(), 7 | VITE_BASE_URL: z.url().default("http://localhost:3000"), 8 | BETTER_AUTH_SECRET: z.string().min(1), 9 | 10 | // OAuth2 providers, optional, update as needed 11 | GITHUB_CLIENT_ID: z.string().optional(), 12 | GITHUB_CLIENT_SECRET: z.string().optional(), 13 | GOOGLE_CLIENT_ID: z.string().optional(), 14 | GOOGLE_CLIENT_SECRET: z.string().optional(), 15 | }, 16 | runtimeEnv: process.env, 17 | }); 18 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | VITE_BASE_URL=http://localhost:3000 2 | 3 | DATABASE_URL="postgresql://user:password@localhost:5432/tanstarter" 4 | # You can also use Docker Compose to set up a local PostgreSQL database: 5 | # docker-compose up -d 6 | 7 | # pnpm run auth:secret 8 | BETTER_AUTH_SECRET= 9 | 10 | # OAuth2 providers, optional, update as needed 11 | GITHUB_CLIENT_ID= 12 | GITHUB_CLIENT_SECRET= 13 | GOOGLE_CLIENT_ID= 14 | GOOGLE_CLIENT_SECRET= 15 | 16 | # NOTE: 17 | # In your OAuth2 apps, set callback/redirect URIs to`http://localhost:3000/api/auth/callback/` 18 | # e.g. http://localhost:3000/api/auth/callback/github -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "base-vega", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/styles.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "iconLibrary": "lucide", 14 | "aliases": { 15 | "components": "~/components", 16 | "utils": "~/lib/utils", 17 | "ui": "~/components/ui", 18 | "lib": "~/lib", 19 | "hooks": "~/hooks" 20 | }, 21 | "menuColor": "default", 22 | "menuAccent": "subtle", 23 | "registries": {} 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "jsx": "react-jsx", 7 | "module": "ESNext", 8 | "moduleResolution": "Bundler", 9 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 10 | "isolatedModules": true, 11 | "resolveJsonModule": true, 12 | "skipLibCheck": true, 13 | "target": "ES2022", 14 | "allowJs": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "paths": { 17 | "~/*": ["./src/*"] 18 | }, 19 | "noEmit": true, 20 | "strictNullChecks": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cn } from "~/lib/utils"; 6 | 7 | function Label({ className, ...props }: React.ComponentProps<"label">) { 8 | return ( 9 |