An authentication and user management service built with NestJS, TypeORM, MySQL, JWT, and Resend email. It provides:
- User registration with email verification
- Email/password login (JWT-based)
- Basic user CRUD endpoints
- Developer tooling: Swagger API docs, Jest tests, ESLint/Prettier, Husky + Conventional Commits
- Overview
- Tech Stack
- Architecture
- Getting Started
- Prerequisites
- Environment Variables
- Run Locally (Node)
- Run with Docker Compose
- API Documentation
- Auth Endpoints
- User Endpoints
- Mail Endpoint
- Authentication & Security Notes
- Testing
- Conventional Commits & Developer Workflow
- Deployment Notes
- Troubleshooting
Trust Auth is a simple, production-ready starting point for auth-driven apps/services. Users register, receive a verification email, verify their account, then log in to obtain a short-lived JWT for protected requests. The service also includes a generic email sender endpoint (via Resend) and basic user CRUD to illustrate typical patterns.
- NestJS 11
- TypeORM 0.3 + MySQL 8
- Passport (Local & JWT strategies)
- Resend (transactional email)
- Swagger (OpenAPI docs)
- Jest (unit + e2e tests)
- src/main.ts: App bootstrap, global prefix /api, Swagger at /swagger
- src/app.module.ts: Root module; TypeORM config; imports UserModule and AuthModule
- src/user: User entity, DTOs, service, controller (CRUD)
- src/auth: Auth controller/service, strategies (local, jwt), guards, email verification flow, MailService and MailController
- src/core: CoreEntity with id/createdAt/updatedAt/deletedDate shared by entities
Database tables are generated automatically (TypeORM synchronize=true in development). The User entity includes fields: name, email (unique), password (hashed), phoneNumber (optional), isVerified (boolean).
- Node.js 18+ and npm/yarn
- Docker (optional but recommended for local MySQL)
You can run with defaults using docker-compose (recommended) or provide env vars. Note: there are a few important names to be aware of.
Database (used by TypeORM in code):
- DATABASE_HOST (default: db when using docker)
- DATABASE_PORT (default: 3306)
- DATABASE_USER (default: admin)
- DATABASE_PASSWORD (default: password)
- DATABASE_NAME (default: authapp)
Email (Resend):
- RESEND_API_KEY (required to send real emails)
- RESEND_FROM_EMAIL (optional; default: onboarding@resend.dev)
JWT and App:
- BASE_URL (required for email verification link generation, e.g. http://localhost:3000)
Notes:
- The sample .env currently contains DB_* keys (DB_HOST, DB_PORT, etc.) which are not used by the TypeORM configuration. If you prefer env-based config outside docker defaults, use DATABASE_* variables instead, or rely on the defaults provided in src/app.module.ts.
- JWT is configured via src/auth/constants.ts and is currently hardcoded (secretKey) for development only. See Security Notes below.
Create a .env file in the project root if needed:
RESEND_API_KEY=your_resend_api_key RESEND_FROM_EMAIL=onboarding@resend.dev BASE_URL=http://localhost:3000
Install dependencies and start the app. You need a running MySQL instance with the database and credentials configured to match the TypeORM config.
- Install deps: yarn install
- Start in watch mode: yarn start:dev
- App runs at: http://localhost:3000
- API prefix: http://localhost:3000/api
- Swagger UI: http://localhost:3000/swagger
- Swagger JSON: http://localhost:3000/swagger-json
If not using Docker, ensure your local MySQL has a database authapp and user admin/password or set DATABASE_* env variables accordingly.
Docker compose includes:
- MySQL 8 (service: db, exposed at host port 8886)
- phpMyAdmin (service: pma, exposed at host port 8899)
- NestJS service (service: nestjs, hot reloading enabled)
Commands:
- docker compose up --build
- App: http://localhost:3000
- Swagger: http://localhost:3000/swagger
- MySQL: host=localhost, port=8886 (inside compose the app connects to db:3306)
- phpMyAdmin: http://localhost:8899 (login: server=db, user=admin, pass=password)
The compose file mounts .env for the NestJS service. For email verification links to be correct, set BASE_URL=http://localhost:3000 in your .env.
Swagger UI is available at /swagger with the live OpenAPI spec. Below are the primary endpoints and sample requests.
Base API prefix for controllers: /api
- Register
- POST /api/auth/register
- Body: { "name": "John Doe", "email": "john@example.com", "password": "StrongPass123" }
- Response (201): { "message": "Created Successfully, please verify your email", "statusCode": 201 }
Behavior:
- Password is hashed with bcrypt.
- If email exists, 400 is returned.
- A verification token is sent via email using Resend. The link is:
${BASE_URL}/auth/verify-email?token=....
- Verify Email
- GET /api/auth/verify-email?token=YOUR_TOKEN
- Response (200): void (email verified). Errors: 401 invalid/expired token, 400 user not found.
- Login
- POST /api/auth/login
- Body: { "email": "john@example.com", "password": "StrongPass123" }
- Response (200): { "message": "Login successful", "token": "" }
Notes:
- Login uses LocalAuthGuard, expects email/password.
- Returned JWT is a Bearer token; default expiry is 60s (dev).
Note: In the current code, user routes are not guarded. In production, add JwtAuthGuard to protect them.
-
POST /api/users Create a user (primarily useful for admin/testing) Body: { name, email, password, phoneNumber? }
-
GET /api/users List all users
-
GET /api/users/:id Get a user by id
-
PATCH /api/users/:id Update a user
-
DELETE /api/users/:id Delete a user
Sample curl for a protected endpoint (if you add guard): curl -H "Authorization: Bearer " http://localhost:3000/api/users
- POST /api/mail/send
- Body: { "to": "someone@example.com", "subject": "Hello", "html": "Hi!" }
- Requires RESEND_API_KEY to be set; uses RESEND_FROM_EMAIL or defaults to onboarding@resend.dev.
- JWT secret is set in src/auth/constants.ts as 'secretKey' for development ONLY. Do not use this in production. For production, update JwtModule.register to read from an environment variable and keep secrets out of source control.
- Access token expiry is currently 60 seconds. Adjust signOptions.expiresIn in src/auth/auth.module.ts.
- Verification tokens expire in 1 hour (see AuthService.register).
- Always use HTTPS and secure cookies/headers in production.
- Unit tests: yarn test
- E2E tests: yarn test:e2e
- Coverage: yarn test:cov
This repo is configured for clean, conventional commits.
- Interactive commits: npm run commit (uses Commitizen)
- Commit message linting: enforced via commitlint
- Pre-commit: lint-staged runs Prettier + ESLint on staged files
- Optional helper: npm run acp to auto-stage, commit, and push (skips main/master)
- Build: yarn build then run node dist/main for production
- Adjust TypeORM config to use environment variables (DATABASE_*) and disable synchronize in production
- Configure a proper JWT secret via environment and rotate regularly
- Set BASE_URL to your public domain so verification links are valid
- I can’t connect to MySQL locally
- Using docker-compose: connect to localhost:8886 from host; inside app it connects to db:3306 automatically.
- If running without docker, set DATABASE_* envs to match your local DB.
- Verification emails are not sent
- Ensure RESEND_API_KEY is set. Check that RESEND_FROM_EMAIL is set or accept the default.
- Login returns 401
- Ensure the user is registered, password is correct, and email is verified if your client expects that logic.
- Swagger not loading
- Confirm app is running: http://localhost:3000 and open http://localhost:3000/swagger