The backend follows standard AdonisJS v6 conventions with some additional patterns specific to Nuda Kit. For general AdonisJS directory structure, see the official AdonisJS documentation.
This page focuses on Nuda Kit-specific patterns you should understand.
backend/
├── app/
│ ├── abilities/ # Bouncer ability definitions
│ ├── controllers/ # HTTP controllers (by domain)
│ ├── events/ # Event dispatchers
│ ├── exceptions/ # Exception handlers
│ ├── jobs/ # Background jobs (BullMQ)
│ ├── listeners/ # Event listeners
│ ├── mails/ # Email notifications
│ ├── middleware/ # HTTP middleware
│ ├── models/ # Lucid ORM models
│ ├── policies/ # Authorization policies
│ ├── services/ # Business logic services
│ └── validators/ # VineJS validation schemas
├── config/ # Configuration files
├── database/
│ ├── migrations/ # Database migrations
│ └── seeders/ # Database seeders
├── resources/views/ # Edge templates (emails)
├── start/
│ ├── routes/ # Route definitions (by domain)
│ ├── events.ts # Event bindings
│ ├── kernel.ts # Middleware stack
│ └── limiter.ts # Rate limiting rules
└── tests/ # Japa test suites
Nuda Kit uses a single controller per action pattern instead of resource controllers:
controllers/
└── users/
├── create_user_controller.ts # POST /users
├── login_user_controller.ts # POST /users/login
├── get_authenticated_user_controller.ts # GET /users/me
└── update_user_detail_controller.ts # PUT /users
Why? Each controller is small, focused, and easy to test. No scrolling through a 500-line resource controller.
Controllers, routes, and tests are organized by domain:
| Domain | Purpose |
|---|---|
users/ | Authentication, profile, settings |
teams/ | Team CRUD, members, avatars |
invitations/ | Team invitation handling |
billing/ | Stripe checkout, portal, invoices |
plans/ | Subscription plans |
ai/ | AI streaming endpoints |
webhooks/ | External webhooks (Stripe) |
controllers/HTTP controllers organized by domain. Each controller handles one action:
// app/controllers/users/create_user_controller.ts
export default class CreateUserController {
public async execute({ request, response }: HttpContext) {
// Handle the request
}
}
models/Lucid ORM models representing database tables:
| Model | Table | Purpose |
|---|---|---|
User | users | User accounts |
Team | teams | Teams/organizations |
TeamMember | team_members | Team membership with roles |
Invitation | invitations | Pending team invitations |
Plan | plans | Subscription plans |
Subscription | subscriptions | User subscriptions |
validators/VineJS validation schemas organized by domain:
validators/
├── user.ts # User-related validations
├── team.ts # Team-related validations
├── billing.ts # Billing validations
└── ai.ts # AI request validations
services/Business logic extracted from controllers:
| Service | Purpose |
|---|---|
AiService | AI provider abstraction and streaming |
StripeService | Stripe API interactions |
events/Event dispatchers for decoupled side effects:
| Event | Triggered When |
|---|---|
UserCreated | New user registers |
UserDeleted | User account deleted |
SendUserVerificationEmail | Email verification needed |
SendUserResetPasswordEmail | Password reset requested |
listeners/Event listeners that respond to dispatched events:
| Listener | Responds To |
|---|---|
CreateTeam | UserCreated — creates default team |
SendVerificationEmail | SendUserVerificationEmail |
SendPasswordResetEmail | SendUserResetPasswordEmail |
EnqueueDeleteUser | UserDeleted — queues deletion job |
jobs/Background jobs processed by BullMQ:
| Job | Purpose |
|---|---|
SendVerificationEmailJob | Send verification email |
SendMagicLinkEmailJob | Send magic link email |
SendResetPasswordEmailJob | Send password reset email |
SendInvitationEmailJob | Send team invitation email |
DeleteUserJob | Permanently delete user data |
mails/Email notification classes:
| Purpose | |
|---|---|
VerifyEmailNotification | Email verification |
MagicLinkEmailNotification | Magic link login |
ResetPasswordEmailNotification | Password reset |
InvitationEmailNotification | Team invitation |
policies/Bouncer authorization policies:
| Policy | Controls |
|---|---|
TeamPolicy | Team access (update, delete, manage members) |
middleware/HTTP middleware in the request pipeline:
| Middleware | Purpose |
|---|---|
AuthMiddleware | Verify authentication |
ForceJsonResponseMiddleware | Always return JSON |
InitializeBouncerMiddleware | Setup authorization |
SwaggerAuthMiddleware | Protect API docs |
routes/Route definitions split by domain:
start/routes/
├── users.ts # /users/*
├── teams.ts # /teams/*
├── invitations.ts # /invitations/*
├── billing.ts # /billing/*
├── plans.ts # /plans/*
├── ai.ts # /ai/*
├── webhooks.ts # /webhooks/*
└── docs.ts # /docs (Swagger)
events.tsBinds events to their listeners:
emitter.on(UserCreated, [CreateTeam])
emitter.on(SendUserVerificationEmail, [SendVerificationEmail])
kernel.tsDefines the middleware stack and named middleware.
limiter.tsRate limiting rules for specific routes.
Key configuration files:
| Config | Purpose |
|---|---|
auth.ts | Authentication guards |
database.ts | PostgreSQL connection |
mail.ts | Email providers (SMTP, Mailgun, Resend) |
drive.ts | File storage (local, S3) |
queue.ts | Redis queue configuration |
ally.ts | Social auth providers |
swagger.ts | API documentation |
migrations/Database migrations in chronological order:
seeders/Database seeders for development and testing:
| Seeder | Purpose |
|---|---|
PlanSeeder | Create subscription plans |
tests/* | Test data seeders |
Tests mirror the controller structure:
tests/functional/
├── users/ # User endpoint tests
├── teams/ # Team endpoint tests
├── billing/ # Billing endpoint tests
├── invitations/ # Invitation endpoint tests
├── plans/ # Plans endpoint tests
└── webhooks/ # Webhook tests
| Type | Convention | Example |
|---|---|---|
| Controllers | snake_case_controller.ts | create_user_controller.ts |
| Models | snake_case.ts | team_member.ts |
| Migrations | timestamp_description.ts | 1756568731173_create_users_table.ts |
| Jobs | snake_case_job.ts | send_verification_email_job.ts |
| Events | snake_case.ts | user_created.ts |
| Validators | snake_case.ts | user.ts |
Nuda Kit uses import aliases for clean imports:
import User from '#models/user'
import { createUserValidator } from '#validators/user'
import CreateUserController from '#controllers/users/create_user_controller'
import SendVerificationEmailJob from '#jobs/send_verification_email_job'
Available aliases are defined in package.json under imports.