After nearly five years of building Evergreen - a comprehensive enterprise platform that handles CRM, business intelligence, call center operations, and client portals—I've accumulated a wealth of lessons that I wish I'd known from day one.
1. Start with the Data Model
The single most important decision you'll make is your data model. Everything else - UI, features, integrations—can be changed relatively easily. Your core data model cannot.
Spend the time upfront to really understand:
- What are the core entities in your domain?
- How do they relate to each other?
- What data will you need for reporting?
- How might requirements evolve?
// Early mistake: Tightly coupled address to customer
interface Customer {
name: string
address: string // 🚫 What about multiple addresses?
city: string
state: string
}
// Better: Flexible, normalized structure
interface Customer {
id: string
name: string
addresses: Address[] // ✅ Supports multiple, typed addresses
primaryAddressId: string
}
interface Address {
id: string
type: 'billing' | 'service' | 'mailing'
street: string
city: string
state: string
zip: string
}
2. Boring Foundation, Strategic Innovation
When I started Evergreen, I was tempted to use cutting-edge patterns everywhere. The key lesson: keep your foundation boring, but don't be afraid to use advanced patterns where they genuinely solve problems.
Our core stack is deliberately boring:
- PostgreSQL for everything database-related (with read replicas for BI)
- Redis for caching and real-time features
- Vue.js/Nuxt for the frontend
- Nest.js for the backend
But we strategically adopted advanced patterns where they made sense:
- Event Sourcing for audit trails and complex workflows where we need complete history
- Microservices for specific high-scale features that benefit from isolation
- Queue-based Processing with BullMQ for background jobs and async operations
The lesson: Choose boring technology for your foundation, but don't dogmatically avoid modern patterns when they solve real problems. The key is being intentional about when to use what.
3. Invest in Developer Experience
Every hour spent improving developer experience pays dividends for years. Some investments that have paid off massively:
Type Safety Everywhere
TypeScript on both frontend and backend with shared types:
// shared/types/lead.ts
export interface Lead {
id: string
firstName: string
lastName: string
email: string
phone: string
source: LeadSource
score: number
createdAt: Date
}
// Used in both frontend and backend
// Compile-time errors when contract changes
Automated Testing
Not 100% coverage - that's diminishing returns. But comprehensive tests for:
- Critical business logic
- API contracts
- Complex UI interactions
Local Development
One command to spin up the entire stack locally:
docker-compose up
# Database, Redis, API, and frontend all running
4. Features Are Forever
Every feature you add becomes a feature you maintain. Forever.
Before adding anything, I now ask:
- Who specifically needs this? "Everyone" is not an answer.
- What problem does it solve? If you can't articulate it, don't build it.
- What's the maintenance burden? Features have ongoing costs.
- Can we solve this with existing features? Often yes.
The best code is code you don't write. The best feature is one you don't build.
5. Communication > Code
As the team grew, I realized that communication became more important than any technical decision.
What helps:
- Architecture Decision Records (ADRs): Document why you made choices, not just what you built
- Runbooks: Step-by-step guides for common operations
- Incident postmortems: Learn from failures without blame
- Regular demos: Show don't tell
Good documentation is a feature. It reduces bus factor, speeds up onboarding, and prevents repeated mistakes.
Looking Ahead
Five years in, Evergreen is more successful than I ever imagined. It's grown from a simple CRM to the nervous system of the entire business.
The next five years will bring new challenges—AI integration, mobile expansion, and scaling the team. But the foundational lessons remain the same:
- Invest in foundations
- Choose simplicity over cleverness
- Communicate relentlessly
- Build for maintainability
The best enterprise software isn't the most technically impressive; it's the software that reliably solves real problems, year after year.