hackx

Backend From Zero

8 / 35

People, and the tokens we email them

You don't need twelve tables. Users. And tokens we mailed (verify, reset). Done.

Stick this under the generator block:

model User {
  id           String   @id @default(cuid())
  email        String   @unique
  passwordHash String
  name         String?
  isVerified   Boolean  @default(false)
  createdAt    DateTime @default(now())
  tokens       EmailToken[]
}

model EmailToken {
  id        String    @id @default(cuid())
  token     String    @unique
  purpose   String    // "verify" | "reset"
  expiresAt DateTime
  usedAt    DateTime?
  createdAt DateTime  @default(now())
  userId    String
  user      User      @relation(fields: [userId], references: [id], onDelete: Cascade)
}

From the repo root. No --config:

npx prisma db push
npx prisma generate

src/db.ts lives next to src/generated/. One extra ../ and TypeScript looks outside the project. Count the dots with us.

Wrong:

import { PrismaClient } from "../../generated/prisma/client.mjs";

The whole file. One client. Don't open a new connection every request, you'll melt Neon.

import { PrismaClient } from "./generated/prisma/client.js";

const prisma = new PrismaClient();

export default prisma;

Routes: import prisma from "../db.ts". Generated client stays .js. Yeah it's inconsistent. Live with it.

The whole .env has to exist on whichever machine is running Node. Laptop = your file. Live = Actions writes every club secret (JWT, APP_URL, AWS keys, MAIL_FROM, Neon), not only DATABASE_URL.