Set up a Next.js app with Drizzle ORM and Kinde Auth
By Peter Phanouvong —
Drizzle is a powerful, open-source ORM that we can’t get enough of at Kinde, so we put together this kit to show you how to get the two working together. Between Next.js, Drizzle, and Kinde you’ll have a powerful back end to get your application started.
This guide will show you how to set up a Next.js app with Drizzle and Kinde Auth.
We will cover the following:
- Setting up a Next.js app with TypeScript and Tailwind
- Integrating Kinde with the Next.js app to handle user authentication
- Saving user data to a local Sqlite3 database with Drizzle
If you ever see this pointing finger emoji “👉” it means there is something for you to do.
Setting up a Next.js app with TypeScript and Tailwind. You can find the official documentation for setting up a Next.js app in the Next.js Installation Guide.
The Next.js team recommends creating a new Next.js app using create-next-app
, which sets up everything automatically for you.
👉 Run the following in your terminal.
npx create-next-app@latest
On installation, you will see the following prompts:
What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias? No / Yes
👉 Keep pressing Enter
or Return
and you will have a Next.js app setup with TypeScript and Tailwind
You can now get your app up and running.
👉 Navigate to the project directory and run:
npm run dev
You should see something like this:
Integrating Kinde with the Next.js app to handle user authentication. You can find the official documentation for this setup on the Kinde Next.js App Router SDK page.
👉 Run the following in your terminal.
npm i @kinde-oss/kinde-auth-nextjs
👉 Create a Kinde account. You can register here - https://app.kinde.com/register
👉 Once logged in, head to Settings > Applications > [Your app] > View details and add your callback URLs. It should look like this:
-
Allowed callback URLs:
http://localhost:3000/api/auth/kinde_callback
-
Allowed logout redirect URLs:
http://localhost:3000
👉 Press the “Save” button.
We need to set some environment variables to connect our Next.js app to our Kinde app.
👉 Create a .env
file in the root directory of your project.
KINDE_CLIENT_ID=<your_kinde_client_id>
KINDE_CLIENT_SECRET=<your_kinde_client_secret>
KINDE_ISSUER_URL=https://<your_kinde_subdomain>.kinde.com
KINDE_SITE_URL=http://localhost:3000
KINDE_POST_LOGOUT_REDIRECT_URL=http://localhost:3000
KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000
👉 Replace the placeholders with your actual client ID, client secret and issuer URL.
You can find these on the Application page on Kinde.
For these .env file variables to take effect, make sure to restart your dev server by stopping the server in your terminal and then re-running
npm run dev
To get Kinde Auth working with our app, we will need to set some authentication endpoints within our app.
👉 Create the file src/app/api/auth/[kindeAuth]/route.ts
.
// route.ts
import {handleAuth} from "@kinde-oss/kinde-auth-nextjs/server";
export const GET = handleAuth();
Now let’s register, login and logout!
We are going to use getUser
and isAuthenticated
from the getKindeServerSession
util function provided by Kinde to see the current user’s authentication status.
Kinde also provides LoginLink
, RegisterLink
and LogoutLink
which can be used to facilitate authentication in your app.
👉 Update app/page.tsx
to look something like this:
import {
RegisterLink,
LoginLink,
getKindeServerSession,
LogoutLink
} from "@kinde-oss/kinde-auth-nextjs/server";
export default function Home() {
const {getUser, isAuthenticated} = getKindeServerSession();
const user = await getUser();
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
{isAuthenticated() ? (
<div>
<p className="mb-8">Well, well, well, it isn't...</p>
<p className="font-medium text-lg">
{user.given_name} {user.family_name}
</p>
<pre className="mt-4 bg-slate-950 text-cyan-200 p-4 font-mono text-sm rounded-sm">
{JSON.stringify(user, null, 2)}
</pre>
<LogoutLink className="inline-block mt-8 underline text-blue-500">
Logout
</LogoutLink>
</div>
) : (
<div className="mb-32 grid text-center lg:mb-0 lg:grid-cols-4 lg:text-left">
<LoginLink className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30">
<h2 className={`mb-3 text-2xl font-semibold`}>
Login{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
->
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Log into an existing Lumon account.
</p>
</LoginLink>
<RegisterLink className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30">
<h2 className={`mb-3 text-2xl font-semibold`}>
Register{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
->
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Create a new Lumon account.
</p>
</RegisterLink>
</div>
)}
</main>
);
}
You should now be able to register, login, and logout of your app!
Saving user data to a local Sqlite3 database with Drizzle.
You can find the official documentation for what I am going to cover in the DrizzleORM docs.
👉 Run this in your terminal:
npm install drizzle-orm better-sqlite3
npm i -D drizzle-kit
👉 Create the file db/schema.ts
in the root directory - in this file you can define your database schema:
// db/schema.ts
import {sqliteTable, text, integer} from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: text("id").primaryKey(),
firstName: text("first_name"),
lastName: text("last_name"),
email: text("email")
});
// OR ALTERNATIVELY
// export const users = sqliteTable("users", {
// id: integer("id").primaryKey(),
// kindeId: text("kinde_id"),
// firstName: text("first_name"),
// lastName: text("last_name"),
// email: text("email"),
// });
Here I have set up a users
table which we will populate later.
The reason why I am using
text
for theid
is because Kinde provides anid
string rather than an integer. Instead of just one ID, you could also track akindeId
as well as your normalid
👉 Create the file db/index.ts
in the root directory - in this file, we will set up and sqlite3
database and connect it to Drizzle.
// db/index.ts
import {drizzle, BetterSQLite3Database} from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import {migrate} from "drizzle-orm/better-sqlite3/migrator";
const sqlite = new Database("sqlite.db");
export const db: BetterSQLite3Database = drizzle(sqlite);
migrate(db, {migrationsFolder: "drizzle"});
👉 Create the file drizzle.config.ts
in the root directory.
// drizzle.config.ts
import type {Config} from "drizzle-kit";
export default {
schema: "./db/schema.ts",
out: "./drizzle",
driver: "better-sqlite",
dbCredentials: {
url: "sqlite.db"
}
} satisfies Config;
👉 Update tsconfig.json
- change the “target” from es5
to es6
. This is because Drizzle does not yet support es5.
// tsconfig.json
{
"compilerOptions": {
"target": "es6",
...
👉 Get the database up and running by running this in your terminal:
npx drizzle-kit generate:sqlite
You should see this:
❯ npx drizzle-kit generate:sqlite
drizzle-kit: v0.19.5
drizzle-orm: v0.27.2
No config path provided, using default 'drizzle.config.ts'
Reading config file '/Users/xxx/drizzle-1/drizzle.config.ts'
2 tables
songs 3 columns 0 indexes 0 fks
users 4 columns 0 indexes 0 fks
To explore the data in your database you can run
npx drizzle-kit studio
On successful login, we are going to check if the user coming back to us from Kinde is in our local database. If not, we will add them to the local database.
👉 Update the .env
file:
KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000/api/auth/success
After login, Kinde will redirect to our app’s API route at /api/auth/success
Make sure to restart the dev server after updating the environment variables.
👉 Create the API route /api/auth/success/route.ts
file:
import {db} from "@/db";
import {users} from "@/db/schema";
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
import {eq} from "drizzle-orm";
import {NextResponse} from "next/server";
export async function GET() {
// check if user exists
const {getUser, getOrganization} = getKindeServerSession();
const user = await getUser();
const {orgCode} = await getOrganization();
if (!user || user == null || !user.id)
throw new Error("something went wrong with authentication" + user);
const dbUser = db.select().from(users).where(eq(users.id, user.id)).get();
// or const dbUser = await db.select().from(users).where(eq(users.id, user.id));
if (!dbUser) {
db.insert(users)
.values({
id: user.id,
firstName: user.given_name,
lastName: user.given_name,
email: user.email
})
.run();
// or
// await db.insert(users)
// .values({
// id: user.id,
// firstName: user.given_name,
// lastName: user.given_name,
// email: user.email,
// });
}
return NextResponse.redirect("http://localhost:3000/");
}
Now if you login or register, your database should get populated with those users!
To double check, you can check the data in your database by running
npx drizzle-kit studio
- look out for the new users in the users table
That’s all you need to get set up with Next.js, Drizzle and Kinde.
In the future, I’ll cover some interesting use cases for this set up, like multi-tenancy and roles and permissions. For now, let us know how you go on Twitter, and if you have any issues contact support@kinde.com or post them in our community.