Documentation Index
Fetch the complete documentation index at: https://mintlify.com/rifandani/be-monorepo/llms.txt
Use this file to discover all available pages before exploring further.
User Management
This guide covers user registration, authentication flows, and user data management in the BE Monorepo.
User Schema
The user table is defined in src/db/schema.ts:
export const userTable = pgTable("user", {
id: text().primaryKey(),
name: text().notNull(),
email: text().notNull().unique(),
emailVerified: boolean().default(false).notNull(),
image: text(),
...timestamps, // createdAt, updatedAt, deletedAt
});
export const selectUserTableSchema = createSelectSchema(userTable);
export type UserTable = z.infer<typeof selectUserTableSchema>;
User Fields
- id: Unique text identifier (primary key)
- name: User’s display name (required)
- email: User’s email address (unique, required)
- emailVerified: Email verification status (default: false)
- image: Optional profile image URL
- timestamps: Automatic created/updated/deleted timestamps
Account Table
The account table manages authentication providers and credentials:
export const accountTable = pgTable("account", {
id: text().primaryKey(),
accountId: text().notNull(),
providerId: text().notNull(),
userId: text()
.notNull()
.references(() => userTable.id, { onDelete: "cascade" }),
accessToken: text(),
refreshToken: text(),
idToken: text(),
accessTokenExpiresAt: timestamp(),
refreshTokenExpiresAt: timestamp(),
scope: text(),
password: text(), // Hashed password for email/password auth
...timestamps,
});
User Registration
Better Auth handles user registration through its built-in API endpoints. Users can register via:
Email and Password
Clients can register by sending a POST request to /api/auth/sign-up/email:
const response = await fetch('http://localhost:3333/api/auth/sign-up/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'secure-password',
name: 'John Doe'
})
});
Response
{
"user": {
"id": "cm5x...",
"email": "user@example.com",
"name": "John Doe",
"emailVerified": false,
"image": null,
"createdAt": "2026-03-04T10:00:00.000Z",
"updatedAt": "2026-03-04T10:00:00.000Z"
},
"session": {
"id": "ses_...",
"userId": "cm5x...",
"expiresAt": "2026-04-04T10:00:00.000Z",
"token": "eyJhbG..."
}
}
User Login
Authenticate existing users through the Better Auth API:
const response = await fetch('http://localhost:3333/api/auth/sign-in/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'secure-password'
})
});
Authentication Flow
- Client sends credentials to
/api/auth/sign-in/email
- Better Auth validates credentials against hashed password in account table
- On success, creates a new session with token
- Returns user data and session token
- Client stores session token (typically in cookies)
Password Handling
Better Auth automatically handles password security:
- Hashing: Passwords are hashed using bcrypt before storage
- Validation: Password strength can be configured
- Storage: Hashed passwords stored in
account.password field
- Comparison: Secure comparison during authentication
Password Security
// Better Auth handles this automatically
// Passwords are:
// 1. Hashed with bcrypt
// 2. Stored in account.password
// 3. Never returned in API responses
// 4. Compared securely during login
User Roles and Permissions
The current implementation uses a simple user model without built-in roles. To add role-based access control:
Extend User Schema
export const userTable = pgTable("user", {
id: text().primaryKey(),
name: text().notNull(),
email: text().notNull().unique(),
emailVerified: boolean().default(false).notNull(),
image: text(),
role: text().default("user").notNull(), // Add role field
...timestamps,
});
Role-Based Middleware
import type { MiddlewareHandler } from "hono";
export function requireRole(role: string): MiddlewareHandler {
return async (c, next) => {
const user = c.get("user");
if (!user) {
return c.json({ error: "Unauthorized" }, 401);
}
if (user.role !== role) {
return c.json({ error: "Forbidden" }, 403);
}
return next();
};
}
Verification Table
Email verification tokens are managed in a separate table:
export const verificationTable = pgTable("verification", {
id: text().primaryKey(),
identifier: text().notNull(), // Email or phone
value: text().notNull(), // Verification token
expiresAt: timestamp().notNull(),
...timestamps,
});
User Operations
Get Current User
app.get("/api/me", authContextMiddleware(), async (c) => {
const user = c.get("user");
if (!user) {
return c.json({ error: "Not authenticated" }, 401);
}
return c.json({ user });
});
Update User Profile
import { db } from "@/db/index.js";
import { userTable } from "@/db/schema.js";
import { eq } from "drizzle-orm";
app.patch("/api/me", authContextMiddleware(), async (c) => {
const user = c.get("user");
if (!user) return c.json({ error: "Unauthorized" }, 401);
const { name, image } = await c.req.json();
const [updated] = await db
.update(userTable)
.set({ name, image, updatedAt: new Date() })
.where(eq(userTable.id, user.id))
.returning();
return c.json({ user: updated });
});
Delete User
app.delete("/api/me", authContextMiddleware(), async (c) => {
const user = c.get("user");
if (!user) return c.json({ error: "Unauthorized" }, 401);
// Soft delete
await db
.update(userTable)
.set({ deletedAt: new Date() })
.where(eq(userTable.id, user.id));
return c.json({ success: true });
});
API Endpoints
Better Auth provides these endpoints automatically:
POST /api/auth/sign-up/email - Register new user
POST /api/auth/sign-in/email - Login user
POST /api/auth/sign-out - Logout user
GET /api/auth/session - Get current session
POST /api/auth/verify-email - Verify email
POST /api/auth/reset-password - Reset password
View full API documentation at /api/auth/docs.
Next Steps