Skip to main content

Overview

This guide will walk you through creating a basic HTTP server using AdonisJS Core. You’ll learn how to use the Ignitor class to bootstrap your application and define routes.
This quickstart assumes you’ve already installed AdonisJS Core. If not, please complete the installation first.

Create Your First Server

Let’s build a simple HTTP server that responds to requests. Follow these steps to get up and running.
1

Create the server entry point

Create a file at bin/server.ts with the following code:
bin/server.ts
import { Ignitor } from '@adonisjs/core'

/**
 * Create a new Ignitor instance with the application root URL.
 * The Ignitor manages the application lifecycle.
 */
const ignitor = new Ignitor(new URL('../', import.meta.url))

/**
 * Start the HTTP server
 */
await ignitor.httpServer().start()
The Ignitor class is the entry point for AdonisJS applications. It handles bootstrapping your app in different environments: web, console, test, or REPL.
2

Configure your application

Create a configuration file at config/app.ts:
config/app.ts
import { defineConfig } from '@adonisjs/core/app'

export default defineConfig({
  appName: 'My AdonisJS App',
  appKey: 'your-secret-app-key-32-chars-long',
})
In production, generate a secure appKey using: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
3

Set up environment variables

Create a .env file in your project root:
.env
HOST=0.0.0.0
PORT=3333
NODE_ENV=development
The HTTP server reads HOST and PORT from environment variables, defaulting to 0.0.0.0:3333 if not specified.
4

Add routes

Modify your bin/server.ts to add routes using the tap method:
bin/server.ts
import { Ignitor } from '@adonisjs/core'

const ignitor = new Ignitor(new URL('../', import.meta.url))

/**
 * Tap into the application instance to configure it
 * before it starts
 */
ignitor.tap((app) => {
  app.booting(async () => {
    // Get the router from the container
    const router = await app.container.make('router')
    
    // Define your routes
    router.get('/', () => {
      return { message: 'Hello from AdonisJS!' }
    })
    
    router.get('/users/:id', ({ params }) => {
      return { user: { id: params.id, name: 'John Doe' } }
    })
    
    router.post('/users', async ({ request }) => {
      const body = request.all()
      return { message: 'User created', data: body }
    })
  })
})

await ignitor.httpServer().start()
The tap method allows you to access the application instance before it boots. The booting lifecycle hook runs during application initialization.
5

Start the server

Run your server with:
node --import=@poppinss/ts-exec bin/server.ts
You should see output like:
started HTTP server on 0.0.0.0:3333
6

Test your server

Open your browser and navigate to http://localhost:3333/ or use curl:
curl http://localhost:3333/
Expected responses:
{"message":"Hello from AdonisJS!"}

Understanding the Code

Let’s break down the key components:

The Ignitor Class

The Ignitor class from @adonisjs/core is the application bootstrapper:
import { Ignitor } from '@adonisjs/core'

const ignitor = new Ignitor(new URL('../', import.meta.url))
From the source code at src/ignitor/main.ts:36, the Ignitor provides methods for different application modes:
  • httpServer() - Start an HTTP server for web requests
  • ace() - Run CLI commands
  • testRunner() - Execute tests
export class Ignitor {
  /**
   * Get instance of the HTTPServerProcess
   */
  httpServer() {
    return new HttpServerProcess(this)
  }

  /**
   * Get an instance of the AceProcess class
   */
  ace() {
    return new AceProcess(this)
  }

  /**
   * Get an instance of the TestRunnerProcess class
   */
  testRunner() {
    return new TestRunnerProcess(this)
  }
}

HTTP Server Process

When you call ignitor.httpServer().start(), the HttpServerProcess class (from src/ignitor/http.ts:32) handles:
  1. Creating the application in ‘web’ environment
  2. Initializing and booting the app
  3. Resolving the HTTP server from the container
  4. Creating a Node.js HTTP server
  5. Listening on the configured host and port
  6. Monitoring the server for graceful shutdown
export class HttpServerProcess {
  async start(serverCallback?: (...) => ...) {
    const app = this.#ignitor.createApp('web')

    await app.init()
    await app.boot()
    await app.start(async () => {
      const server = await app.container.make('server')
      await server.boot()

      const httpServer = createHTTPServer(server.handle.bind(server))
      server.setNodeServer(httpServer)

      const { port, host } = await this.#listen(httpServer)
      // Server is now running...
    })
  }
}

Application Lifecycle

The tap method allows you to hook into the application lifecycle:
ignitor.tap((app) => {
  app.booting(async () => {
    // Runs during application boot
  })
})

Adding More Features

Now that you have a basic server running, let’s explore additional features:

Route Groups

Organize related routes together:
router.group(() => {
  router.get('/users', () => ({ users: [] }))
  router.get('/users/:id', ({ params }) => ({ user: { id: params.id } }))
  router.post('/users', () => ({ message: 'Created' }))
}).prefix('/api/v1')

Middleware

Add middleware to routes:
router.get('/admin', () => {
  return { message: 'Admin dashboard' }
}).middleware(async (ctx, next) => {
  // Add authentication logic
  console.log(`Request to ${ctx.request.url()}`)
  await next()
})

Request Validation

Validate incoming requests (requires @vinejs/vine):
import vine from '@vinejs/vine'

const userSchema = vine.object({
  name: vine.string(),
  email: vine.string().email(),
})

router.post('/users', async ({ request }) => {
  const data = await request.validateUsing(userSchema)
  return { message: 'Valid user data', data }
})

Error Handling

Handle errors gracefully:
router.get('/error', () => {
  throw new Error('Something went wrong!')
})

// The framework will automatically catch and format the error

Production Build

To build your application for production:
1

Compile TypeScript

npm run build
2

Run the compiled server

node build/bin/server.js

Next Steps

Congratulations! You’ve created your first AdonisJS HTTP server. Here’s what to explore next:

Routing

Learn about advanced routing features, named routes, and route resources

Controllers

Organize your route handlers into controller classes

Middleware

Create reusable middleware for authentication, CORS, and more

Validation

Validate requests with VineJS integration

Application Lifecycle

Understand the boot process and lifecycle hooks

Service Providers

Learn how to register and configure application services

Common Patterns

For larger applications, move routes to a separate file:
start/routes.ts
import router from '@adonisjs/core/services/router'

router.get('/', () => ({ message: 'Hello' }))
router.get('/users/:id', ({ params }) => ({ user: { id: params.id } }))
Then import it in your bin/server.ts:
bin/server.ts
ignitor.tap((app) => {
  app.booting(() => import('#start/routes'))
})
You can pass a custom server factory to handle HTTPS:
import { createServer } from 'node:https'
import { readFileSync } from 'node:fs'

await ignitor.httpServer().start((handler) => {
  return createServer({
    key: readFileSync('server.key'),
    cert: readFileSync('server.cert')
  }, handler)
})
The HTTP server handles graceful shutdown automatically:
process.on('SIGTERM', async () => {
  await ignitor.terminate()
})

Need Help?

Join our community or open an issue on GitHub if you need assistance.