The batteries-included TypeScript framework
Everything you need in one Node.js framework. Authentication, ORM, validation, mail, queues, cache, testing - all working together. Built for teams who want to ship products, not assemble frameworks.
Code you'll actually enjoy writing
AdonisJS combines expressive APIs with clear conventions and full TypeScript support. Explore the examples below to see how common tasks stay simple without sacrificing power
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'
import { controllers } from '#generated/controllers'
router
.group(() => {
router.get('login', [controllers.Session, 'create'])
router.post('login', [controllers.Session, 'store'])
router.get('signup', [controllers.NewAccount, 'create'])
router.post('signup', [controllers.NewAccount, 'store'])
})
.use(middleware.guest())
// Creates a total of 7 routes to manage posts
router.resource('posts', controllers.Posts)
import Post from '#models/post'
import { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async index({ request }: HttpContext) {
const posts = await Post.query().preload('author').paginate(1, 20)
return posts
}
async store({ request, auth, response }: HttpContext) {
const data = request.validateUsing(createPostValidator)
const post = await auth.user.related('posts').create(data)
return response.created(post)
}
}
import vine from '@vinejs/vine'
export const createPostValidator = vine.create({
title: vine.string().minLength(3).maxLength(255),
content: vine.string().minLength(10),
coverImage: vine.file({
size: '2mb',
extnames: ['jpg', 'png', 'webp']
}).optional()
})
export const createUserValidator = vine.create({
fullName: vine.string().nullable(),
email: vine.string().email().unique({ table: 'users' }),
password: vine.string().confirmed()
})
import User from '#models/user'
import Comment from '#models/comment'
import { PostSchema } from '#database/schema'
// ...other imports
export default class Post extends PostSchema {
@belongsTo(() => User)
author: BelongsTo<typeof User>
@hasMany(() => Comment)
comments: HasMany<typeof Comment>
}
await Post.all()
await Post.query().preload('author').where('id', 1).firstOrFail()
await Post.query().withCount('comments').paginate(1, 20)
// ...imports
export default class AuthController {
async login({ request, auth }: HttpContext) {
const user = await User.verifyCredentials(email, password)
// Create session for user
await auth.use('web').login(user)
}
async loginWithToken({ request }: HttpContext) {
const user = await User.verifyCredentials(email, password)
// Create token for user
const token = await User.accessTokens.create(user)
return { token: token.value!.release() }
}
}
// ...imports
export default class AssetsController {
async store({ request, response }: HttpContext) {
const { file } = await request.validateUsing(createFileValidator)
const fileName = `${uuid()}.${file.extname}`
await file.moveToDisk(fileName, 'r2')
return response.created({ fileName, url: `/assets/${fileName}` })
}
async show({ params, response }: HttpContext) {
const stream = await drive.use('r2').getStream(params.fileName)
return response.stream(stream)
}
}
// ...imports
export default class WelcomeMail extends BaseMail {
subject = 'Welcome to Acme!'
constructor(protected user: User) {
super()
}
prepare() {
this.message
.to(this.user.email)
.htmlView('emails/welcome', { user: this.user })
}
}
// Usage example
await mail.send(new WelcomeMail(user))
import limiter from '@adonisjs/limiter/services/main'
export const apiThrottle = limiter.define('api', ({ auth, request }) => {
if (auth.user) {
const key = `user_${auth.user.id}`
return limiter.allowRequests(100).every('1 minute').usingKey(key)
}
const key = `ip_${request.ip()}`
return limiter.allowRequests(10).every('1 minute').usingKey(key)
})
// Usage example
router
.get('api/repos', () => {})
.use(apiThrottle)
import { test } from '@japa/runner'
import { UserFactory } from '#factories/user'
test.group('Posts store', (group) => {
test('create post as authenticated user', async ({ client }) => {
const user = await UserFactory.create()
const response = await client
.visit('posts.store')
.loginAs(user)
.json({ title: 'Hello World', content: 'My first post' })
response.assertStatus(201)
response.assertBodyContains(postData)
})
})
loginAs can be used to make authenticated requests
The framework you've been looking for
Developers are discovering what Laravel and Rails fans have known for years - batteries-included frameworks just work better
Let me say this again!
AdonisJS is the only full-stack framework in JavaScript land.
🙌🙌🙌🙌
Once you've tried @adonisframework
– DB setup in minutes
– Auth ready with @adonisjs/auth
– Fullstack with @inertiajs React
– TypeScript & type-safe out of the box
I honestly wonder: why do people still use Express.js today ?
After spending years working with Laravel, I recently decided to explore AdonisJS, and honestly, it feels like discovering Laravel's long-lost cousin in the JavaScript world.
Having a solid experience in Laravel and wanting to explore NodeJs, I found @adonisframework a good choice for me to go through. It is been easy for me to jump in since it feels like I am just writing Laravel but in typescript and NodeJS, Awesome developer experience!.
If you're a PHP dev who loves Laravel and want to give another language a go and use a similar style framework then make sure to check out @adonisframework. It has its own ORM, Auth support, and even a CLI tool called Ace which is very similar to Artisan.#Laravel #NodeJS
Adonis is wonderful - just a perfect level of cognitive load a framework can have both in documentation and implementation.
Not too much, not too little, but exactly what you need as a developer.
Everything you need to build
Powerful developer tooling. Official packages for common requirements. Flexible architecture that adapts to your needs. Built to take you from prototype to production.
Developer experience that just works
Pretty error pages, powerful CLI, security primitives, seamless Vite integration, and end-to-end type safety. The tooling feels invisible until you need it.
Official packages, zero hunting
From caching to rate limiting to health checks, common requirements solved with official packages. No npm rabbit holes, no compatibility roulette
View all packagesMVC with a flexible view layer
AdonisJS gives you complete MVC architecture, but the View is up to you. Pair your models and controllers with React, Vue, server-rendered templates, or build a pure API. Structured backend, flexible frontend.
Our approach towards frontendimport User from '#models/user'
import type { HttpContext } from '@adonisjs/core/http'
import UserTransformer from '#transformers/user_transformer'
export default class UsersController {
async index({ request, inertia }: HttpContext) {
const users = await User.all()
return inertia.render('users/index', {
// props serialization with type inference
users: UserTransformer.transform(users)
})
}
}
import User from '#models/user'
import type { HttpContext } from '@adonisjs/core/http'
import UserTransformer from '#transformers/user_transformer'
export default class UsersController {
async index({ request, inertia }: HttpContext) {
const users = await User.all()
return inertia.render('users/index', {
// props serialization with type inference
users: UserTransformer.transform(users)
})
}
}
import User from '#models/user'
import type { HttpContext } from '@adonisjs/core/http'
export default class UsersController {
async index({ request, view }: HttpContext) {
const users = await User.all()
return view.render('users/index', {
users
})
}
}
import User from '#models/user'
import type { HttpContext } from '@adonisjs/core/http'
import UserTransformer from '#transformers/user_transformer'
export default class UsersController {
async index({ request, serialize }: HttpContext) {
const users = await User.all()
return serialize(UserTransformer.transform(users))
}
}
<script setup lang="ts">
import { Data } from '~/generated/data'
defineProps<{ users: Data.User[] }>()
</script>
<template>
<div v-for="user in users" :key="user.id">
<p>{{ user.email }}</p>
</div>
</template>
import { InertiaProps } from '~/types'
import { Data } from '~/generated/data'
type PageProps = InertiaProps<{ users: Data.User[] }>
export default function UsersIndex({ users }: PageProps) {
return <>
{users.map((user) => (
<div key={user.id}>
<p>{user.email}</p>
</div>
))}
</>
}
@each(user in users)
<div>
<p>{{ user.email }}</p>
<div>
@end
{
"data": [
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
}
]
}
Releases
ChangelogGoing strong for over a decade
No hype, no chasing trends. Just regular updates and commitment to building something that lasts
MIT LicensedProudly supported by our partners
These companies and individuals keep AdonisJS thriving. Their monthly support funds development, maintenance, and community growth - allowing us to stay independent and focused on building the best framework we can
Become a partner - 300k monthly visitsReady to build?
Whether you're exploring AdonisJS for the first time or ready to build your next product, here's how to get started