Skip to main content

Your first Elysia app

Let’s build a simple REST API to learn the fundamentals of Elysia. We’ll create an API that manages a list of users with full type safety and validation.
Make sure you’ve installed Elysia before continuing.

Basic server

Create a new file called index.ts and add the following code:
index.ts
import { Elysia } from 'elysia'

const app = new Elysia()
  .get('/', () => 'Hello Elysia')
  .listen(3000)

console.log(
  `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
)
Run your server:
bun run index.ts
Visit http://localhost:3000 and you’ll see “Hello Elysia”!

Adding routes

Let’s add more routes to handle different HTTP methods:
index.ts
import { Elysia } from 'elysia'

const app = new Elysia()
  .get('/', () => 'Hello Elysia')
  .get('/json', () => ({ message: 'Hello World', timestamp: Date.now() }))
  .post('/mirror', ({ body }) => body)
  .listen(3000)
Elysia automatically handles JSON serialization. Return an object and it becomes a JSON response.

Schema validation

Now let’s add type-safe validation using Elysia’s built-in schema system:
index.ts
import { Elysia, t } from 'elysia'

const app = new Elysia()
  .post('/user', ({ body }) => {
    // body is automatically typed as { name: string, age: number }
    return { 
      message: `Created user ${body.name}, age ${body.age}`,
      user: body 
    }
  }, {
    body: t.Object({
      name: t.String(),
      age: t.Number()
    })
  })
  .listen(3000)
Test it with curl:
curl -X POST http://localhost:3000/user \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "age": 25}'
Try sending invalid data - Elysia will automatically return a validation error!

Building a REST API

Let’s build a complete REST API with in-memory storage:
index.ts
import { Elysia, t } from 'elysia'

interface User {
  id: number
  name: string
  email: string
}

const users: User[] = []
let nextId = 1

const app = new Elysia()
  // Get all users
  .get('/users', () => users)
  
  // Get user by ID
  .get('/users/:id', ({ params: { id }, set }) => {
    const user = users.find(u => u.id === Number(id))
    
    if (!user) {
      set.status = 404
      return { error: 'User not found' }
    }
    
    return user
  })
  
  // Create a new user
  .post('/users', ({ body }) => {
    const user: User = {
      id: nextId++,
      name: body.name,
      email: body.email
    }
    
    users.push(user)
    return user
  }, {
    body: t.Object({
      name: t.String(),
      email: t.String({ format: 'email' })
    })
  })
  
  // Update a user
  .put('/users/:id', ({ params: { id }, body, set }) => {
    const index = users.findIndex(u => u.id === Number(id))
    
    if (index === -1) {
      set.status = 404
      return { error: 'User not found' }
    }
    
    users[index] = { ...users[index], ...body }
    return users[index]
  }, {
    body: t.Partial(t.Object({
      name: t.String(),
      email: t.String({ format: 'email' })
    }))
  })
  
  // Delete a user
  .delete('/users/:id', ({ params: { id }, set }) => {
    const index = users.findIndex(u => u.id === Number(id))
    
    if (index === -1) {
      set.status = 404
      return { error: 'User not found' }
    }
    
    users.splice(index, 1)
    return { message: 'User deleted successfully' }
  })
  
  .listen(3000)

console.log(
  `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
)

Testing your API

1

Create a user

curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "[email protected]"}'
2

Get all users

curl http://localhost:3000/users
3

Get a specific user

curl http://localhost:3000/users/1
4

Update a user

curl -X PUT http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Smith"}'
5

Delete a user

curl -X DELETE http://localhost:3000/users/1

State management

For more complex state, use Elysia’s state management:
import { Elysia } from 'elysia'

const app = new Elysia()
  .state('counter', 0)
  .derive(({ store }) => ({
    increase() {
      store.counter++
    }
  }))
  .get('/count', ({ increase, store }) => {
    increase()
    return { 
      counter: store.counter,
      doubled: store.counter * 2 
    }
  })
  .listen(3000)
Each request to /count will increment the counter.

Route grouping and guards

Organize routes and apply shared validation with groups and guards:
import { Elysia, t } from 'elysia'

const app = new Elysia()
  .group('/api', (app) => app
    .guard(
      {
        headers: t.Object({
          authorization: t.String()
        })
      },
      (app) => app
        .get('/protected', ({ headers }) => ({
          message: 'You are authenticated!',
          token: headers.authorization
        }))
        .get('/profile', () => ({ user: 'John Doe' }))
    )
    .get('/public', () => 'This route is public')
  )
  .listen(3000)
Test the protected route:
curl http://localhost:3000/api/protected \
  -H "Authorization: Bearer your-token"

Error handling

Handle errors gracefully with error hooks:
import { Elysia, t } from 'elysia'

const app = new Elysia()
  .post('/user', ({ body }) => {
    if (body.age < 18) {
      throw new Error('User must be 18 or older')
    }
    return { message: 'User created', user: body }
  }, {
    body: t.Object({
      name: t.String(),
      age: t.Number()
    }),
    error({ error, set }) {
      set.status = 400
      return { 
        error: error.message,
        timestamp: Date.now()
      }
    }
  })
  .listen(3000)

Lifecycle hooks

Add middleware logic with lifecycle hooks:
import { Elysia } from 'elysia'

const app = new Elysia()
  .onRequest(({ request }) => {
    console.log(`${request.method} ${request.url}`)
  })
  .onBeforeHandle(({ set }) => {
    set.headers['X-Powered-By'] = 'Elysia'
  })
  .get('/', () => 'Hello Elysia')
  .listen(3000)

Next steps

Now that you’ve built your first Elysia application, explore these topics:

Routing

Learn advanced routing patterns and dynamic routes.

Validation

Deep dive into schema validation and type safety.

Plugins

Extend your app with the plugin ecosystem.

State & decorators

Manage state and add custom functionality.

Guards & groups

Organize routes and apply shared logic.

Error handling

Handle errors and validation failures.
Check out the example directory in the Elysia repository for more real-world examples.

Build docs developers (and LLMs) love