Migrating from AdonisJS to tekir
Both frameworks share similar conventions: controllers, providers, middleware, and a structured project layout. tekir runs on Bun instead of Node.js.
Overview
AdonisJS and tekir share a lot of DNA. Both are TypeScript frameworks with controllers, providers, middleware, and a structured project layout. The main differences: tekir runs on Bun, uses tekir() as a single entry point, uses functional controllers by default (decorators optional), and works with any validation library instead of VineJS.
1. Entry point
AdonisJS uses Ignitor to bootstrap from bin/server.ts. tekir uses tekir() which returns the full app instance.
// AdonisJS v6
// bin/server.ts
import { Ignitor } from '@adonisjs/core'
const app = new Ignitor(import.meta.url)
await app.tap((app) => { import('#start/env') })
.httpServer()
.start()2. Routes
AdonisJS uses lazy controller imports with tuple syntax. tekir imports controllers directly as modules and passes functions to the router.
// AdonisJS v6, start/routes.ts
import router from '@adonisjs/core/services/router'
const UsersController = () => import('#controllers/users_controller')
router.get('/api/users', [UsersController, 'index'])
router.post('/api/users', [UsersController, 'store'])
router.get('/api/users/:id', [UsersController, 'show'])
router.group(() => {
router.get('/profile', [ProfileController, 'show'])
router.put('/profile', [ProfileController, 'update'])
}).prefix('/api').use(middleware.auth())3. Controllers
AdonisJS controllers are classes with request.validateUsing(). tekir controllers are plain exported functions that call any validation library directly.
// AdonisJS v6, app/controllers/users_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
import { createUserValidator } from '#validators/user'
export default class UsersController {
async index({ response }: HttpContext) {
const users = await User.all()
return response.ok(users)
}
async store({ request, response }: HttpContext) {
const data = await request.validateUsing(createUserValidator)
const user = await User.create(data)
return response.created(user)
}
async show({ params, response }: HttpContext) {
const user = await User.findOrFail(params.id)
return response.ok(user)
}
}4. Config & Env
AdonisJS uses defineConfig() wrappers and Env.schema. tekir uses plain objects for config and defineEnv() from @tekir/env for typed environment validation.
// AdonisJS v6, config/app.ts
import env from '#start/env'
import { defineConfig } from '@adonisjs/core/http'
export default defineConfig({
appKey: env.get('APP_KEY'),
http: { ... }
})
// AdonisJS v6: start/env.ts
import { Env } from '@adonisjs/core/env'
export default await Env.create(new URL('../', import.meta.url), {
APP_KEY: Env.schema.string(),
PORT: Env.schema.number(),
NODE_ENV: Env.schema.enum(['development', 'production', 'test'])
})5. Middleware
AdonisJS middleware are classes with a handle method. tekir middleware are plain functions with the same (ctx, next)signature and less boilerplate.
// AdonisJS v6, app/middleware/auth_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class AuthMiddleware {
async handle({ auth, response }: HttpContext, next: NextFn) {
try {
await auth.authenticate()
} catch {
return response.unauthorized({ error: 'Not authenticated' })
}
return next()
}
}6. Providers
Both use providers to register services. tekir providers are plain classes with register(app) and no base class.
// AdonisJS v6, providers/app_provider.ts
import type { ApplicationService } from '@adonisjs/core/types'
export default class AppProvider {
constructor(protected app: ApplicationService) {}
register() {
this.app.container.singleton('userService', () => {
return new UserService()
})
}
async boot() {}
async start() {}
async shutdown() {}
}7. Testing
AdonisJS uses Japa with a clientplugin. tekir uses Bun's built-in test runner with createTestApp(). The assertion style is similar.
// AdonisJS v6, tests/functional/users.spec.ts
import { test } from '@japa/runner'
test.group('Users', () => {
test('list users', async ({ client }) => {
const response = await client.get('/api/users')
response.assertStatus(200)
})
test('create user', async ({ client }) => {
const response = await client.post('/api/users').json({
name: 'Alice',
email: '[email protected]'
})
response.assertStatus(201)
response.assertBodyContains({ name: 'Alice' })
})
})For more details: