{"id":21471570,"url":"https://github.com/nrjdalal/onset","last_synced_at":"2025-08-20T10:32:07.386Z","repository":{"id":205565833,"uuid":"713577287","full_name":"nrjdalal/onset","owner":"nrjdalal","description":"An open source Next.js bare starter with step-by-step instructions if required. Built with Next.js 14, Drizzle (Postgres), NextAuth/Auth.js.","archived":false,"fork":false,"pushed_at":"2024-06-17T17:34:26.000Z","size":572,"stargazers_count":57,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-06T17:48:30.536Z","etag":null,"topics":["drizzle","neondb","nextauthjs","nextjs","postgres","shadcn","tailwindcss","typescript","vercel"],"latest_commit_sha":null,"homepage":"https://onset.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nrjdalal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-02T20:02:22.000Z","updated_at":"2024-11-13T10:30:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"69cb8817-3cd8-4825-b5be-00c63a32b374","html_url":"https://github.com/nrjdalal/onset","commit_stats":null,"previous_names":["nrjdalal/onset"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrjdalal%2Fonset","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrjdalal%2Fonset/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrjdalal%2Fonset/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nrjdalal%2Fonset/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nrjdalal","download_url":"https://codeload.github.com/nrjdalal/onset/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230415318,"owners_count":18222158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["drizzle","neondb","nextauthjs","nextjs","postgres","shadcn","tailwindcss","typescript","vercel"],"created_at":"2024-11-23T09:36:45.259Z","updated_at":"2024-12-19T10:09:25.897Z","avatar_url":"https://github.com/nrjdalal.png","language":"TypeScript","funding_links":[],"categories":["Boilerplates \u0026 Starters"],"sub_categories":[],"readme":"\u003ca href=\"https://nextjs.org\"\u003e\n  \u003ch1 align=\"center\"\u003eOnset Next.js Starter 2024\u003c/h1\u003e\n\u003c/a\u003e\n\n\u003cp align=\"center\"\u003e\n  An open source Next.js starter with step-by-step instructions if required.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://twitter.com/nrjdalal_com\"\u003e\n    \u003cimg src=\"https://img.shields.io/twitter/follow/nrjdalal_com?style=flat\u0026label=nrjdalal_com\u0026logo=twitter\u0026color=0bf\u0026logoColor=fff\" alt=\"Follow Neeraj on Twitter\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#features\"\u003e\u003cstrong\u003eFeatures\u003c/strong\u003e\u003c/a\u003e ·\n  \u003ca href=\"#step-by-step\"\u003e\u003cstrong\u003eStep by Step\u003c/strong\u003e\u003c/a\u003e ·\n  \u003ca href=\"#roadmap\"\u003e\u003cstrong\u003eRoadmap\u003c/strong\u003e\u003c/a\u003e ·\n  \u003ca href=\"#author\"\u003e\u003cstrong\u003eAuthor\u003c/strong\u003e\u003c/a\u003e ·\n  \u003ca href=\"#credits\"\u003e\u003cstrong\u003eCredits\u003c/strong\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nOnset is a Next.js starter that comes with step-by-step instructions to understand how everything works, easy for both beginners and experts alike and giving you the confidence to customize it to your needs. Built with Next.js 14, Drizzle (Postgres), NextAuth/Auth.js.\n\n\u003c!-- About: An open source Next.js bare starter with step-by-step instructions if required. Built with Next.js 14, Drizzle (Postgres), NextAuth/Auth.js. --\u003e\n\u003c!-- Keywords: drizzle neondb nextauthjs nextjs postgres shadcn tailwindcss typescript vercel --\u003e\n\n## Features\n\n### Frameworks\n\n- [Next.js](https://nextjs.org/) – React framework for building performant apps with the best developer experience\n- [Auth.js](https://authjs.dev/) – Handle user authentication with ease with providers like Google, Twitter, GitHub, etc.\n- [Drizzle](https://orm.drizzle.team/) – Typescript-first ORM for Node.js\n\n### Platforms\n\n- [Vercel](https://vercel.com/) – Easily preview \u0026 deploy changes with git\n- [Neon](https://neon.tech/) – The fully managed serverless Postgres with a generous free tier\n\n## Automatic Setup\n\n### Installation\n\nClone \u0026 create this repo locally with the following command:\n\n\u003e Note: You can use `npx` or `pnpx` as well\n\n```bash\nbunx create-next-app onset-starter --example \"https://github.com/nrjdalal/onset\"\n```\n\n1. Install dependencies using pnpm:\n\n```sh\nbun install\n```\n\n2. Copy `.env.example` to `.env.local` and update the variables.\n\n```sh\ncp .env.example .env.local\n```\n\n3. Run the database migrations:\n\n```sh\nbun db:push\n```\n\n3. Start the development server:\n\n```sh\nbun dev\n```\n\n## Step by Step\n\n\u003e Hint: Using `bun` instead of `npm`/`pnpm` and `bunx` instead of `npx`/`pnpx`. You can use the latter if you want.\n\n### Phase 1 (Initialization)\n\n#### 1. Initialize the project\n\nRefs:\n\n- [Installation](https://nextjs.org/docs/getting-started/installation)\n\n```sh\nbunx create-next-app . --ts --eslint --tailwind --src-dir --app --import-alias \"@/*\"\n```\n\n#### 2. Install `prettier` and supporting plugins\n\nRefs:\n\n- [prettier-plugin-sort-imports](https://github.com/IanVS/prettier-plugin-sort-imports)\n- [prettier](https://prettier.io/)\n- [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss)\n\n```sh\nbun add -D @ianvs/prettier-plugin-sort-imports prettier prettier-plugin-tailwindcss\n```\n\n#### 3. Create `prettier.config.js`\n\n```js\n/** @type {import('prettier').Config} */\nmodule.exports = {\n  semi: false,\n  singleQuote: true,\n  plugins: [\n    '@ianvs/prettier-plugin-sort-imports',\n    'prettier-plugin-tailwindcss',\n  ],\n}\n```\n\n#### 4. Create `src/lib/fonts.ts`\n\nRefs:\n\n- [Font Optimization](https://nextjs.org/docs/app/building-your-application/optimizing/fonts)\n\n```ts\nimport {\n  JetBrains_Mono as FontMono,\n  DM_Sans as FontSans,\n} from 'next/font/google'\n\nexport const fontMono = FontMono({\n  subsets: ['latin'],\n  variable: '--font-mono',\n})\n\nexport const fontSans = FontSans({\n  subsets: ['latin'],\n  variable: '--font-sans',\n})\n```\n\n#### 5. Install `clsx`, `tailwind-merge` and `nanoid`\n\nRefs:\n\n- [clsx](https://github.com/lukeed/clsx)\n- [tailwind-merge](https://github.com/dcastil/tailwind-merge)\n\n```sh\nbun add clsx tailwind-merge nanoid\n```\n\n#### 6. Create `src/lib/utils.ts`\n\n```ts\nimport { clsx, type ClassValue } from 'clsx'\nimport { customAlphabet } from 'nanoid'\nimport { twMerge } from 'tailwind-merge'\n\nexport const cn = (...inputs: ClassValue[]) =\u003e {\n  return twMerge(clsx(inputs))\n}\n\nexport function generateId(\n  {\n    chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',\n    length = 12,\n  }: {\n    chars: string\n    length: number\n  } = {\n    chars: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',\n    length: 12,\n  },\n) {\n  const nanoid = customAlphabet(chars, length)\n  return nanoid()\n}\n```\n\n#### 7. Update `src/app/layout.tsx`\n\n```ts\nimport './globals.css'\nimport { fontMono, fontSans } from '@/lib/fonts'\nimport { cn } from '@/lib/utils'\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Onset',\n  description: 'The only Next.js starter you need',\n}\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode\n}) {\n  return (\n    \u003chtml lang=\"en\"\u003e\n      \u003cbody\n        className={cn(\n          'min-h-dvh font-sans antialiased',\n          fontMono.variable,\n          fontSans.variable,\n        )}\n      \u003e\n        {children}\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n### Phase 2 (Database)\n\n#### 1. Install `drizzle` and supporting packages\n\nRefs:\n\n- [Drizzle Postgres](https://orm.drizzle.team/docs/quick-postgresql/postgresjs)\n\n```sh\nbun add drizzle-orm postgres\nbun add -D drizzle-kit\n```\n\n#### 2. Create `src/lib/database.ts`\n\nRefs:\n\n- [Drizzle NextAuth Schema](https://authjs.dev/getting-started/adapters/drizzle)\n\n```ts\nimport {\n  integer,\n  pgTable,\n  primaryKey,\n  text,\n  timestamp,\n} from 'drizzle-orm/pg-core'\nimport { drizzle, PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\n\nconst queryClient = postgres(process.env.POSTGRES_URL as string)\nexport const db: PostgresJsDatabase = drizzle(queryClient)\n\nexport const users = pgTable('user', {\n  id: text('id')\n    .primaryKey()\n    .$defaultFn(() =\u003e crypto.randomUUID()),\n  publicId: text('publicId').unique().notNull(),\n  name: text('name'),\n  email: text('email').notNull(),\n  emailVerified: timestamp('emailVerified', { mode: 'date' }),\n  image: text('image'),\n})\n\nexport const accounts = pgTable(\n  'account',\n  {\n    userId: text('userId')\n      .notNull()\n      .references(() =\u003e users.id, { onDelete: 'cascade' }),\n    type: text('type').notNull(),\n    provider: text('provider').notNull(),\n    providerAccountId: text('providerAccountId').notNull(),\n    refresh_token: text('refresh_token'),\n    access_token: text('access_token'),\n    expires_at: integer('expires_at'),\n    token_type: text('token_type'),\n    scope: text('scope'),\n    id_token: text('id_token'),\n    session_state: text('session_state'),\n  },\n  (account) =\u003e ({\n    compoundKey: primaryKey({\n      columns: [account.provider, account.providerAccountId],\n    }),\n  }),\n)\n\nexport const sessions = pgTable('session', {\n  id: text('id').notNull(),\n  sessionToken: text('sessionToken').primaryKey(),\n  userId: text('userId')\n    .notNull()\n    .references(() =\u003e users.id, { onDelete: 'cascade' }),\n  expires: timestamp('expires', { mode: 'date' }).notNull(),\n})\n\nexport const verificationTokens = pgTable(\n  'verificationToken',\n  {\n    identifier: text('identifier').notNull(),\n    token: text('token').notNull(),\n    expires: timestamp('expires', { mode: 'date' }).notNull(),\n  },\n  (vt) =\u003e ({\n    compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),\n  }),\n)\n```\n\n#### 3. Create `drizzle.config.ts`\n\n```ts\nimport type { Config } from 'drizzle-kit'\n\nexport default {\n  schema: './src/lib/database.ts',\n  dialect: 'postgresql',\n  dbCredentials: {\n    url: process.env.POSTGRES_URL as string,\n  },\n} satisfies Config\n```\n\n#### 4. Copy `.env.local.example` to `.env.local`\n\n\u003e Hint: You can use [`pglaunch`](https://github.com/nrjdalal/pglaunch) to generate a postgres url\n\n```env\nPOSTGRES_URL=\"**********\"\n```\n\n#### 5. Update `package.json`\n\n```json\n{\n  // ...\n  \"scripts\": {\n    // ...\n    \"db:push\": \"bun --env-file=.env.local drizzle-kit push\",\n    \"db:studio\": \"bun --env-file=.env.local drizzle-kit studio\"\n  }\n  // ...\n}\n```\n\n##### 6. Run `db:push` to create tables\n\n```sh\nbun db:push\n```\n\n### Phase 3 (Authentication)\n\n#### 1. Install `next-auth`\n\n```sh\nbun add next-auth@beta @auth/drizzle-adapter\n```\n\n#### 2. Update `.env.local`\n\n```env\n# ...\nAUTH_SECRET=\"**********\"\n\nAUTH_GITHUB_ID=\"**********\"\nAUTH_GITHUB_SECRET=\"**********\"\n```\n\n3. Create `src/lib/auth.ts`\n\n```ts\nimport { db, users } from '@/lib/database'\nimport { generateId } from '@/lib/utils'\nimport { DrizzleAdapter } from '@auth/drizzle-adapter'\nimport { eq } from 'drizzle-orm'\nimport NextAuth from 'next-auth'\nimport GitHub from 'next-auth/providers/github'\n\nexport const { handlers, signIn, signOut, auth } = NextAuth({\n  adapter: {\n    ...DrizzleAdapter(db),\n    async createUser(user) {\n      return await db\n        .insert(users)\n        .values({\n          ...user,\n          publicId: generateId(),\n        })\n        .returning()\n        .then((res) =\u003e res[0])\n    },\n  },\n  providers: [GitHub],\n  session: {\n    strategy: 'jwt',\n  },\n  callbacks: {\n    async session({ session, token }) {\n      if (token) {\n        session.user.id = token.id as string\n        session.user.publicId = token.publicId as string\n        session.user.name = token.name as string\n        session.user.email = token.email as string\n        session.user.image = token.image as string\n      }\n\n      return session\n    },\n\n    async jwt({ token, user }) {\n      const [result] = await db\n        .select()\n        .from(users)\n        .where(eq(users.email, token.email as string))\n        .limit(1)\n\n      if (!result) {\n        if (user) {\n          token.id = user.id\n        }\n\n        return token\n      }\n\n      return {\n        id: result.id,\n        publicId: result.publicId,\n        name: result.name,\n        email: result.email,\n        image: result.image,\n      }\n    },\n  },\n})\n\ndeclare module 'next-auth' {\n  interface Session {\n    user: {\n      id: string\n      publicId: string\n      name: string\n      email: string\n      image: string\n    }\n  }\n}\n```\n\n#### 3. Create `src/app/api/auth/[...nextauth]/route.ts`\n\n```ts\nimport { handlers } from '@/lib/auth'\n\nexport const { GET, POST } = handlers\n```\n\n#### 4. Create `src/middleware.ts` - not supported yet\n\n```ts\nimport { getToken } from 'next-auth/jwt'\nimport { withAuth } from 'next-auth/middleware'\nimport { NextResponse } from 'next/server'\n\nexport default withAuth(\n  async function middleware(req) {\n    const token = await getToken({ req })\n\n    const isAuth = !!token\n    const isAuthPage = req.nextUrl.pathname.startsWith('/access')\n\n    if (isAuthPage) {\n      if (isAuth) {\n        return NextResponse.redirect(new URL('/dashboard', req.url))\n      }\n\n      return null\n    }\n\n    if (!isAuth) {\n      let from = req.nextUrl.pathname\n      if (req.nextUrl.search) {\n        from += req.nextUrl.search\n      }\n\n      return NextResponse.redirect(\n        new URL(`/access?from=${encodeURIComponent(from)}`, req.url),\n      )\n    }\n  },\n  {\n    callbacks: {\n      async authorized() {\n        return true\n      },\n    },\n  },\n)\n\nexport const config = {\n  matcher: ['/access', '/dashboard/:path*'],\n}\n```\n\n#### 5. Create `src/app/(auth)/access/page.tsx`\n\n```tsx\nimport { auth, signIn } from '@/lib/auth'\nimport { redirect } from 'next/navigation'\n\nconst Page = async () =\u003e {\n  const session = await auth()\n  if (session) return redirect('/dashboard')\n\n  return (\n    \u003cdiv className=\"flex min-h-[100dvh] flex-col items-center justify-center gap-8\"\u003e\n      \u003cform\n        action={async () =\u003e {\n          'use server'\n          await signIn('github')\n        }}\n      \u003e\n        \u003cbutton className=\"rounded-md border px-8 py-2.5\" type=\"submit\"\u003e\n          Signin with GitHub\n        \u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default Page\n```\n\n#### 6. Create `src/app/(admin)/dashboard/page.tsx`\n\n```tsx\nimport { auth, signOut } from '@/lib/auth'\nimport { redirect } from 'next/navigation'\n\nconst Page = async () =\u003e {\n  const session = await auth()\n  if (!session) return redirect('/access')\n\n  return (\n    \u003cdiv className=\"flex min-h-[100dvh] flex-col items-center justify-center gap-8\"\u003e\n      \u003cdiv className=\"text-center\"\u003e\n        {Object.entries(session.user).map(([key, value]) =\u003e (\n          \u003cp key={key}\u003e\n            \u003cspan className=\"font-bold\"\u003e{key}\u003c/span\u003e: {value}\n          \u003c/p\u003e\n        ))}\n      \u003c/div\u003e\n\n      \u003cform\n        action={async () =\u003e {\n          'use server'\n          await signOut()\n        }}\n      \u003e\n        \u003cbutton className=\"rounded-md border px-8 py-2\" type=\"submit\"\u003e\n          Sign Out\n        \u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default Page\n```\n\n## Roadmap\n\n- [ ] Light and dark mode\n- [ ] To added fine-grained instructions\n- [ ] More features and points to be added\n\n## Author\n\nCreated by [@nrjdalal](https://twitter.com/nrjdalal_com) in 2023, released under the [MIT license](https://github.com/nrjdalal/onset/blob/main/LICENSE.md).\n\n## Credits\n\nThis project is inspired by [@shadcn](https://twitter.com/shadcn)'s [Taxonomy](https://github.com/shadcn-ui/taxonomy).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnrjdalal%2Fonset","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnrjdalal%2Fonset","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnrjdalal%2Fonset/lists"}