Connect/disconnect messages were flooding production logs. Changed to log.debug level for cleaner output. |
||
|---|---|---|
| .. | ||
| prisma | ||
| src | ||
| bun.lock | ||
| Dockerfile | ||
| jest.config.js | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
CA Grow Ops Manager — Backend
Version: 0.1.0
Stack: TypeScript, Node.js, Express/Fastify, PostgreSQL, Prisma
Overview
The backend is a feature-first, domain-driven Node.js application that provides typed HTTP/JSON APIs for all platform features. It uses Prisma ORM for type-safe database access and follows strict security and testing practices.
Tech Stack
- Language: TypeScript 5.x
- Runtime: Node.js 20.x LTS
- Framework: Express or Fastify (to be decided during implementation)
- Database: PostgreSQL 15.x
- ORM: Prisma 5.x
- Authentication: JWT (access + refresh tokens)
- Testing: Jest + Supertest
- Validation: Zod or Joi
- Logging: Winston or Pino
- Email: SendGrid or Mailgun
Project Structure
backend/
├── src/
│ ├── modules/ # Feature-first modules
│ │ ├── cultivation-ops/ # Tasks, batches, rooms
│ │ │ ├── tasks/
│ │ │ │ ├── tasks.controller.ts
│ │ │ │ ├── tasks.service.ts
│ │ │ │ ├── tasks.schema.ts
│ │ │ │ └── tasks.test.ts
│ │ │ ├── batches/
│ │ │ └── rooms/
│ │ ├── compliance/ # Documents, audit packets
│ │ ├── labor/ # Timeclock, hours, reports
│ │ ├── inventory/ # Materials, lots, transactions
│ │ ├── integrations/ # METRC, environmental, hardware
│ │ ├── communications/ # Comments, announcements, notifications
│ │ └── auth/ # Authentication, authorization
│ ├── shared/ # Shared utilities
│ │ ├── middleware/ # Auth, validation, error handling
│ │ ├── utils/ # Helpers, formatters
│ │ └── types/ # Shared TypeScript types
│ ├── prisma/ # Prisma schema and migrations
│ │ ├── schema.prisma
│ │ └── migrations/
│ ├── config/ # Configuration
│ │ ├── database.ts
│ │ ├── auth.ts
│ │ └── email.ts
│ ├── app.ts # Express/Fastify app setup
│ └── server.ts # Server entry point
├── tests/ # Integration tests
│ ├── setup.ts
│ └── integration/
├── .env.example # Environment variables template
├── package.json
├── tsconfig.json
└── README.md
Domain Modules
Each module follows this structure:
module-name/
├── entity.controller.ts # HTTP route handlers
├── entity.service.ts # Business logic
├── entity.schema.ts # Validation schemas (Zod/Joi)
├── entity.test.ts # Unit tests
└── index.ts # Module exports
Example: Tasks Module
// tasks.controller.ts
export const getTasks = async (req, res) => {
const tasks = await tasksService.findAll(req.query);
res.json(tasks);
};
// tasks.service.ts
export const findAll = async (filters) => {
return prisma.task.findMany({ where: filters });
};
// tasks.schema.ts
export const createTaskSchema = z.object({
name: z.string().min(1),
dueDate: z.string().datetime(),
assigneeId: z.string().optional(),
});
API Design
RESTful Conventions
GET /api/tasks- List tasksGET /api/tasks/:id- Get task detailPOST /api/tasks- Create taskPATCH /api/tasks/:id- Update taskDELETE /api/tasks/:id- Delete task
Response Format
{
"success": true,
"data": { ... },
"meta": {
"page": 1,
"pageSize": 20,
"total": 100
}
}
Error Format
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{ "field": "dueDate", "message": "Required" }
]
}
}
Authentication & Authorization
JWT-Based Auth
- Access Token: 15-minute expiry, stored in httpOnly cookie
- Refresh Token: 7-day expiry, stored in localStorage
- Token Payload:
{ userId, roleId, iat, exp }
RBAC (Role-Based Access Control)
Roles:
Owner: Full accessCompliance Manager: Full compliance, read-only operationsHead Grower: Full cultivation ops, read-only complianceStaff: Limited access to tasks and timeclockAccountant: Read-only reports
Middleware:
export const requireRole = (roles: string[]) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
Database (Prisma)
Schema Example
model Task {
id String @id @default(cuid())
name String
description String?
status TaskStatus @default(PENDING)
dueDate DateTime
assigneeId String?
assignee User? @relation(fields: [assigneeId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum TaskStatus {
PENDING
IN_PROGRESS
COMPLETE
OVERDUE
}
Migrations
# Create migration
npx prisma migrate dev --name add_tasks_table
# Apply migrations
npx prisma migrate deploy
# Generate Prisma Client
npx prisma generate
Testing
Unit Tests (Jest)
describe('TasksService', () => {
it('should create a task', async () => {
const task = await tasksService.create({
name: 'Water plants',
dueDate: new Date(),
});
expect(task.name).toBe('Water plants');
});
});
Integration Tests (Supertest)
describe('POST /api/tasks', () => {
it('should create a task', async () => {
const res = await request(app)
.post('/api/tasks')
.send({ name: 'Water plants', dueDate: '2025-12-10' })
.expect(201);
expect(res.body.data.name).toBe('Water plants');
});
});
Environment Variables
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/ca_grow_ops
# Auth
JWT_SECRET=your-secret-key
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d
# Email
EMAIL_SERVICE=sendgrid
EMAIL_API_KEY=your-api-key
EMAIL_FROM=noreply@example.com
# Integrations
METRC_API_KEY=your-metrc-key
METRC_API_URL=https://api-ca.metrc.com
# Server
PORT=3000
NODE_ENV=development
Development Workflow
Setup
# Install dependencies
npm install
# Set up environment variables
cp .env.example .env
# Run database migrations
npx prisma migrate dev
# Seed database (optional)
npx prisma db seed
# Start dev server
npm run dev
Scripts
{
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src --ext .ts",
"format": "prettier --write src",
"migrate": "prisma migrate dev",
"migrate:deploy": "prisma migrate deploy",
"prisma:generate": "prisma generate"
}
}
Security Best Practices
- Input Validation: Validate all inputs with Zod/Joi
- SQL Injection: Use Prisma parameterized queries
- XSS: Sanitize user inputs; use Content Security Policy
- CSRF: Use CSRF tokens for state-changing operations
- Rate Limiting: Limit API requests per user/IP
- Audit Logging: Log all critical operations
Deployment
Docker
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npx prisma generate
RUN npm run build
CMD ["npm", "start"]
Docker Compose
version: '3.8'
services:
backend:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://user:password@db:5432/ca_grow_ops
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: ca_grow_ops
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Next Steps
- Choose Framework: Decide between Express and Fastify
- Set Up Project: Initialize Node.js project with TypeScript
- Configure Prisma: Set up Prisma schema and migrations
- Implement Auth: Build authentication and RBAC
- Build Modules: Implement feature modules per spec
- Write Tests: Unit and integration tests for all modules
- Deploy: Set up CI/CD and deploy to staging