Ever stare at a blank Express app wondering how to organize 50+ routes without creating spaghetti? Or tried NestJS and felt suffocated by decorators and magic?
Codex JS sits right in the middle.
It's TypeScript-first, decorator-light, and gives Express the clean architecture it always deserved, without turning into a full-blown enterprise framework.
The Stack That Makes Sense
@Repo()
class UserRepository {
async findById(id: string) { /* DB query */ }
}
@Service()
class UserService {
constructor(private userRepo: UserRepository) {}
async getUser(id: string) {
return this.userRepo.findById(id)
}
}
@Controller('/api/users')
class UserController {
constructor(private userService: UserService) {}
@Get('/:id')
async getUser(req: Request) {
return this.userService.getUser(req.params.id)
}
}
@Module({
controllers: [UserController],
providers: [UserService, UserRepository]
})
class UserModule {}
const app = codex()
.enableJson()
.registerModules([UserModule])
.listen(3000)
That's it. Model → Repository → Service → Controller. Each layer has one job. No boilerplate. No config files.
What You Actually Get
Real Dependency Injection
No manual wiring. No circular dependency nightmares. Just inject and it works.
@Service()
class EmailService {
async send(to: string, subject: string) { /* ... */ }
}
@Service()
class UserService {
constructor(private emailService: EmailService) {}
async createUser(data: any) {
const user = await this.save(data)
await this.emailService.send(user.email, 'Welcome!')
return user
}
}
Middleware Where You Need It
Global, module-level, controller, or route-specific. You decide where control happens.
// Module-level auth
app.registerModules([{
route: '/api',
middlewares: [authenticate, rateLimit],
modules: [UserModule]
}])
// Route-level caching
@Get('/', [cacheMiddleware])
getExpensiveData() { /* ... */ }
Type-Safe Request Extensions
Auth middleware that actually works with TypeScript:
type AuthRequest = ExtendedRequest<'user', { id: string; role: string }>
type ProtectedAuthRequest = ProtectedRequest<AuthRequest>
@Get('/profile', [authenticate])
getProfile(req: ProtectedAuthRequest) {
// TypeScript knows req.user exists and is non-nullable
return { userId: req.user.id }
}
Automatic Promise Handling
Return data. Codex handles the JSON response. Throw errors. Codex catches them. No more manual res.json() or try/catch everywhere.
@Get('/:id')
async getUser(req: Request) {
// Just return the data
return this.userService.findById(req.params.id)
}
Still Just Express
Need raw Express? It's right there.
const expressApp = app.instance()
app.use(helmet())
app.use(morgan('combined'))
// Direct Express routes when needed
app.get('/health', (req, res) => {
res.json({ status: 'ok' })
})
The Architecture
Codex enforces clean layers without being dogmatic:
- Repositories handle data access
- Services contain business logic
- Controllers handle HTTP requests
- Modules organize features
Each module is self-contained. Your app stays maintainable as it grows. No "utils" folder with 400 random functions.
Why It Exists
I built Codex JS because I was tired of choosing between:
- Express: Total freedom = total chaos after 20 routes
- NestJS: Clean structure = decorated to death
Codex JS gives you structure when you need it, and gets out of your way when you don't.
It's powered by Express and TypeDI under the hood. No reinventing the wheel. Just better organization.
Get Started
npx create-codex-app my-app
# or add to existing project
npm install codex-js-core
Built as a solo passion project. Started as a personal tool, evolved into something worth sharing. Still plenty to improve, but it works well enough to be useful. Feedback welcome.
Top comments (0)