Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 

Repository files navigation

⚡ Express + Prisma + BetterAuth

A production-ready backend boilerplate with TypeScript, ESM, PostgreSQL, and authentication — configured and ready to ship.

TypeScript Express Prisma BetterAuth PostgreSQL ESM


✦ Stack

Layer Technology
Runtime Node.js 20+
Language TypeScript (ESM)
Framework Express
ORM Prisma + @prisma/adapter-pg
Auth BetterAuth
Database PostgreSQL
Bundler tsup

1. Project Initialization

Create Project Directory

Create a new directory for your project and navigate into it:

mkdir hello-prisma
cd hello-prisma

Initialize TypeScript Project

Initialize a simplified package.json and install the necessary TypeScript dependencies:

npm init -y
npm install typescript tsx @types/node --save-dev
npx tsc --init

2. Install Dependencies

Install the core dependencies for the server, database, and utilities:

# Production dependencies
npm install prisma @prisma/client @prisma/adapter-pg dotenv express cors cookie-parser better-auth

# Development dependencies
npm install --save-dev @types/express @types/cors @types/cookie-parser tsup

3. Configuration

Configure TypeScript (tsconfig.json)

Update your tsconfig.json to support ESM (ECMAScript Modules) and stricter type checking:

{
  "compilerOptions": {
    "outDir": "./dist",
    "module": "ESNext",
    "target": "ES2023",
    "moduleResolution": "bundler",
    "types": [],
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "strict": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "generated/prisma"]
}

Enable ESM in package.json

Add the following key to your package.json to enable module support:

{
  "type": "module"
}

4. Database Setup (Prisma)

Initialize Prisma

Initialize Prisma with PostgreSQL support. This creates a prisma directory and a .env file.

npx prisma init --db --output ../generated/prisma

Configure Prisma Client (src/lib/prisma.ts)

Create a standardized Prisma client instance with the PostgreSQL adapter for optimal performance.

File: src/lib/prisma.ts

import "dotenv/config";
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../generated/prisma/client';

const connectionString = `${process.env.DATABASE_URL}`;

const adapter = new PrismaPg({ connectionString });
const prisma = new PrismaClient({ adapter });

export { prisma };

5. Better Auth Setup

Better Auth provides a comprehensive authentication solution. Configuring it correctly is crucial for security and performance.

Environment Variables

Add the following to your .env file:

# Database connection string
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"

# Better Auth Secret (Generate using `openssl rand -base64 32`)
BETTER_AUTH_SECRET=your_generated_secret_key

# Base URL of your application
BETTER_AUTH_URL=http://localhost:3000

Auth Configuration (src/lib/auth.ts)

Create the authentication instance with professional configuration options.

File: src/lib/auth.ts

import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { prisma } from "./prisma";

export const auth = betterAuth({
    database: prismaAdapter(prisma, {
        provider: "postgresql",
    }),
    emailAndPassword: {
        enabled: true
    },
    session: {
        cookieCache: {
            enabled: true,
            maxAge: 5 * 60, // 5 minutes
        },
    },
    advanced: {
        cookiePrefix: "better-auth",
        useSecureCookies: process.env.NODE_ENV === "production",
        crossSubDomainCookies: {
            enabled: false,
        },
        disableCSRFCheck: true, 
        defaultCookieAttributes: {
            sameSite: "none",
            secure: true,
            httpOnly: false
        }
    }
});

Configuration Explanation

  • session.cookieCache: Enables caching for session cookies to improve performance by reducing database lookups for session validation.

    • enabled: Activates the cache.
    • maxAge: Sets the cache duration (e.g., 5 minutes), balancing performance with data freshness.
  • advanced.cookiePrefix: Sets a custom prefix ("better-auth") for all cookies generated by the library, preventing naming collisions with other services.

  • advanced.useSecureCookies: Enforces the Secure flag on cookies in production environments, ensuring they are only transmitted over HTTPS.

  • advanced.defaultCookieAttributes: Defines global defaults for cookie security.

    • sameSite: "none": Allows cross-site requests (necessary if frontend/backend are on different domains).
    • secure: true: Ensures cookies are only sent over encrypted connections.
    • httpOnly: false: Allows client-side JavaScript to access the cookie (configured here as false, but typically true is recommended for security unless specific client-side access is required).

6. Schema Organization & Migration

For a cleaner project structure, organize your Prisma schema files.

  1. Create Schema Directory: Create a folder named schema inside the prisma directory.
  2. Move Schema File: Move your schema.prisma file into prisma/schema.
  3. Update Configuration: Ensure your package.json or Prisma config points to the new schema location if necessary (e.g., "prisma": { "schema": "prisma/schema" }).

Generate Auth Schema

Use the Better Auth CLI to generate the authentication schema directly from your configuration:

npx @better-auth/cli@latest generate --output ./prisma/schema/auth.prisma --config ./src/lib/auth.ts

Run Migration

Apply the changes to your database:

npx prisma migrate dev

7. Express Application Setup

App Configuration (src/app.ts)

Set up the Express application with CORS, Middleware, and the Better Auth handler.

File: src/app.ts

import express, { Application } from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import { toNodeHandler } from "better-auth/node";
import { auth } from "./lib/auth";

const app: Application = express();

// Middleware
app.use(cookieParser());
app.use(express.json());

// CORS Setup
const allowedOrigins = ["http://localhost:3000"].filter(Boolean);

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin) return callback(null, true);
      
      const isAllowed =
        allowedOrigins.includes(origin) ||
        /^https:\/\/.*\.vercel\.app$/.test(origin); // Allow Vercel deployments

      if (isAllowed) {
        callback(null, true);
      } else {
        callback(new Error(`Origin ${origin} not allowed by CORS`));
      }
    },
    credentials: true,
    methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
    allowedHeaders: ["Content-Type", "Authorization", "Cookie"],
    exposedHeaders: ["Set-Cookie"],
  })
);

// Better Auth API Route
app.all('/api/auth/*', toNodeHandler(auth));

// Health Check Route
app.get("/", (_req, res) => {
  res.status(200).json({
    success: true,
    message: "Server is running successfully",
    service: "Backend API",
    version: "1.0.0",
    environment: process.env.NODE_ENV ?? "development",
    uptime: process.uptime(),
    timestamp: new Date().toISOString(),
  });
});

export default app;

Server Entry Point (src/server.ts)

Create the server entry point to connect to the database and start the application.

File: src/server.ts

import app from "./app";
import { prisma } from "./lib/prisma";

const PORT = process.env.PORT || 5000;

async function main() {
    try {
        await prisma.$connect();
        console.log("Connected to the database successfully.");

        app.listen(PORT, () => {
            console.log(`Server is running on http://localhost:${PORT}`);
        });
    } catch (error) {
        console.error("An error occurred:", error);
        await prisma.$disconnect();
        process.exit(1);
    }
}

main();

8. Final Configuration

Update Scripts (package.json)

Add the following scripts to your package.json to streamline development and deployment:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "npx tsx watch src/server.ts",
  "seeding": "npx tsx src/scripts/seedingAdmin.ts",
  "migrate": "npx prisma migrate dev",
  "generate": "npx prisma generate",
  "postinstall": "prisma generate",
  "build": "prisma generate && tsup src/server.ts --format esm --platform node --target node20 --outDir api --external pg-native"
}

Run Development Server

Start your server in development mode:

npm run dev
Built with TypeScript · ESM · Express · Prisma · BetterAuth

About

This guide walks you through setting up a modern Express backend using: TypeScript (ESM) Prisma ORM (PostgreSQL adapter) BetterAuth-ready architecture CORS + Cookie-based authentication support

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors