DEV Community

Cover image for Codex JS: Where Express gets discipline, and Nest loses its chains.
Clancy
Clancy

Posted on

Codex JS: Where Express gets discipline, and Nest loses its chains.

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)
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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() { /* ... */ }
Enter fullscreen mode Exit fullscreen mode

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 }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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' })
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Full docs on GitHub →


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)