Quick Start
This guide walks you through adding the auth domain to an existing TypeScript project. By the end you will have a working user registration and login system with typed errors, port interfaces, and a Prisma adapter — all in your repository.
Step 1 — Initialize Backcap
Section titled “Step 1 — Initialize Backcap”If you have not already initialized Backcap in your project:
npx @backcap/cli initAccept the detected framework and package manager, or select them manually. A backcap.json file will be created in your project root.
Step 2 — Add the Auth Domain
Section titled “Step 2 — Add the Auth Domain”npx @backcap/cli add authThe CLI will:
- Fetch the
authdomain bundle from the registry - Check for any file conflicts with your existing codebase
- Ask you to confirm the installation
- Write the domain source files to
src/domains/auth/ - Install any required npm dependencies
- Record
authin yourbackcap.json
What Gets Written
Section titled “What Gets Written”src/domains/auth/ domain/ entities/ user.entity.ts # User aggregate root value-objects/ email.vo.ts # Email with RFC-5321 validation password.vo.ts # Password with strength validation errors/ invalid-email.error.ts invalid-credentials.error.ts user-not-found.error.ts user-already-exists.error.ts events/ user-registered.event.ts __tests__/ user.entity.test.ts email.vo.test.ts password.vo.test.ts errors.test.ts user-registered.event.test.ts application/ use-cases/ register-user.use-case.ts login-user.use-case.ts ports/ user-repository.port.ts # IUserRepository interface password-hasher.port.ts # IPasswordHasher interface token-service.port.ts # ITokenService interface dto/ register-input.dto.ts login-input.dto.ts login-output.dto.ts __tests__/ register-user.use-case.test.ts login-user.use-case.test.ts mocks/ user-repository.mock.ts password-hasher.mock.ts token-service.mock.ts fixtures/ user.fixture.ts contracts/ auth.contract.ts # IAuthService interface auth.factory.ts # createAuthService() factory index.ts shared/ result.ts # Result<T, E> monadStep 3 — Implement the Port Interfaces
Section titled “Step 3 — Implement the Port Interfaces”The auth domain defines three port interfaces that you must implement. These are the seams between the domain logic and your infrastructure:
IUserRepository
Section titled “IUserRepository”export interface IUserRepository { findByEmail(email: string): Promise<User | null>; save(user: User): Promise<void>; findById(id: string): Promise<User | null>;}If you are using Prisma, implement IUserRepository by writing a PrismaUserRepository class. For example:
import type { IUserRepository } from "../../domains/auth/application/ports/user-repository.port";// ... implement save, findByEmail, findById using your Prisma clientIPasswordHasher
Section titled “IPasswordHasher”Implement this interface using bcrypt or argon2:
import bcrypt from "bcrypt";import type { IPasswordHasher } from "../../domains/auth/application/ports/password-hasher.port";
export class BcryptPasswordHasher implements IPasswordHasher { async hash(plain: string): Promise<string> { return bcrypt.hash(plain, 10); }
async compare(plain: string, hash: string): Promise<boolean> { return bcrypt.compare(plain, hash); }}ITokenService
Section titled “ITokenService”Implement using jsonwebtoken or any JWT library:
import jwt from "jsonwebtoken";import type { ITokenService } from "../../domains/auth/application/ports/token-service.port";
export class JwtTokenService implements ITokenService { constructor(private readonly secret: string) {}
async generate(userId: string, roles: string[]): Promise<string> { return jwt.sign({ userId, roles }, this.secret, { expiresIn: "7d" }); }
async verify(token: string): Promise<{ userId: string } | null> { try { return jwt.verify(token, this.secret) as { userId: string }; } catch { return null; } }}Step 4 — Wire the Service
Section titled “Step 4 — Wire the Service”Use the createAuthService factory from the contracts layer to assemble the service:
import { createAuthService } from "./domains/auth/contracts";import { PrismaUserRepository } from "./adapters/prisma/auth/user-repository.adapter";import { BcryptPasswordHasher } from "./adapters/my-app/password-hasher.adapter";import { JwtTokenService } from "./adapters/my-app/token-service.adapter";import { prisma } from "./lib/prisma"; // your Prisma client
export const authService = createAuthService({ userRepository: new PrismaUserRepository(prisma), passwordHasher: new BcryptPasswordHasher(), tokenService: new JwtTokenService(process.env.JWT_SECRET!),});Step 5 — Use the Service
Section titled “Step 5 — Use the Service”Call the service from your route handlers or controllers:
// Registrationconst result = await authService.register({ email: "user@example.com", password: "securepassword1",});
if (result.isFail()) { const error = result.unwrapError(); // error is typed: UserAlreadyExists | InvalidEmail | Error console.error(error.message); return;}
const { userId } = result.unwrap();
// Loginconst loginResult = await authService.login({ email: "user@example.com", password: "securepassword1",});
if (loginResult.isOk()) { const { token, userId } = loginResult.unwrap();}Step 6 — Add an HTTP Router (Optional)
Section titled “Step 6 — Add an HTTP Router (Optional)”If you are using Express (or any other framework), implement an HTTP adapter that calls your authService. For example:
import express from "express";import { authService } from "./container";
const app = express();app.use(express.json());
app.post("/auth/register", async (req, res) => { const result = await authService.register(req.body); if (result.isFail()) { return res.status(400).json({ error: result.unwrapError().message }); } return res.status(201).json(result.unwrap());});You own this wiring code — implement it in whatever way fits your framework.
Next Steps
Section titled “Next Steps”- Read the Auth domain reference for the full API
- Learn about the Result pattern
- See how to create your own domain