Skip to main content

Overview

This tutorial walks you through creating the simplest possible Motia application: a “Hello World” API that accepts HTTP requests and processes them in the background. What you’ll learn:
  • Creating HTTP-triggered Steps
  • Enqueuing background jobs
  • Using queue-triggered Steps
  • Working with state storage
  • Structured logging

Prerequisites

Before starting, make sure you have:
  • Node.js version 19 or higher
  • Bun installed (or npm/pnpm)

Project Setup

1

Create a new project

Create a new directory and initialize your project:
mkdir hello-world-app
cd hello-world-app
npm init -y
2

Install dependencies

npm install motia zod
npm install -D typescript @types/node
Update your package.json to include:
{
  "type": "module",
  "scripts": {
    "dev": "iii",
    "build": "motia build",
    "start": "bun run --enable-source-maps dist/index-production.js"
  }
}
3

Create project structure

mkdir -p steps

Building the Application

Step 1: Create the HTTP API Step

Create steps/hello-api.step.ts:
steps/hello-api.step.ts
import type { Handlers, StepConfig } from 'motia'
import { z } from 'zod'

export const config = {
  name: 'HelloAPI',
  description: 'Receives hello request and enqueues event for processing',
  triggers: [
    {
      type: 'http',
      path: '/hello',
      method: 'GET',
      responseSchema: {
        200: z.object({
          message: z.string(),
          status: z.string(),
          appName: z.string(),
        }),
      },
    },
  ],
  enqueues: ['process-greeting'],
  flows: ['hello-world-flow'],
} as const satisfies StepConfig

export const handler: Handlers<typeof config> = async (_, { enqueue, logger }) => {
  const appName = 'Hello World App'
  const timestamp = new Date().toISOString()

  logger.info('Hello API endpoint called', { appName, timestamp })

  await enqueue({
    topic: 'process-greeting',
    data: {
      timestamp,
      appName,
      greetingPrefix: process.env.GREETING_PREFIX || 'Hello',
      requestId: Math.random().toString(36).substring(7),
    },
  })

  return {
    status: 200,
    body: {
      message: 'Hello request received! Check logs for processing.',
      status: 'processing',
      appName,
    },
  }
}
Key concepts:
  • Triggers: The http trigger defines a GET endpoint at /hello
  • Response schema: Zod schema validates the response structure
  • Enqueues: Declares this Step can enqueue messages to the process-greeting topic
  • Flows: Groups related Steps together for organization

Step 2: Create the Background Processing Step

Create steps/process-greeting.step.ts:
steps/process-greeting.step.ts
import type { Handlers, StepConfig } from 'motia'
import { z } from 'zod'

const inputSchema = z.object({
  timestamp: z.string(),
  appName: z.string(),
  greetingPrefix: z.string(),
  requestId: z.string(),
})

export const config = {
  name: 'ProcessGreeting',
  description: 'Processes greeting in the background',
  triggers: [
    {
      type: 'queue',
      topic: 'process-greeting',
      input: inputSchema,
    },
  ],
  enqueues: [],
  flows: ['hello-world-flow'],
} as const satisfies StepConfig

export const handler: Handlers<typeof config> = async (input, { logger, state }) => {
  const { timestamp, appName, greetingPrefix, requestId } = input

  logger.info('Processing greeting', { requestId, appName })

  const greeting = `${greetingPrefix} ${appName}!`

  await state.set('greetings', requestId, {
    greeting,
    processedAt: new Date().toISOString(),
    originalTimestamp: timestamp,
  })

  logger.info('Greeting processed successfully', {
    requestId,
    greeting,
    storedInState: true,
  })
}
Key concepts:
  • Queue trigger: Listens to the process-greeting topic
  • Input validation: Zod schema validates incoming queue messages
  • State storage: Persists data using state.set(bucket, key, value)
  • Structured logging: Includes contextual data with log messages

Running the Application

1

Start the development server

npm run dev
The server will start and display available endpoints.
2

Test the API

Open a new terminal and make a request:
curl http://localhost:3000/hello
You should see:
{
  "message": "Hello request received! Check logs for processing.",
  "status": "processing",
  "appName": "Hello World App"
}
3

Check the logs

In your terminal running the dev server, you’ll see:
[HelloAPI] Hello API endpoint called
[ProcessGreeting] Processing greeting
[ProcessGreeting] Greeting processed successfully

Customization

Add Environment Variables

Create a .env file:
GREETING_PREFIX=Welcome
Now requests will use “Welcome” instead of “Hello”.

Retrieve Stored Greetings

Add a new Step to retrieve greetings from state:
steps/get-greeting.step.ts
import type { Handlers, StepConfig } from 'motia'
import { z } from 'zod'

export const config = {
  name: 'GetGreeting',
  description: 'Retrieves a stored greeting',
  triggers: [
    {
      type: 'http',
      path: '/greeting/:requestId',
      method: 'GET',
      responseSchema: {
        200: z.object({
          greeting: z.string(),
          processedAt: z.string(),
          originalTimestamp: z.string(),
        }),
        404: z.object({ error: z.string() }),
      },
    },
  ],
  enqueues: [],
  flows: ['hello-world-flow'],
} as const satisfies StepConfig

export const handler: Handlers<typeof config> = async (
  { request },
  { state, logger }
) => {
  const { requestId } = request.pathParams || {}
  
  const greeting = await state.get('greetings', requestId)
  
  if (!greeting) {
    return {
      status: 404,
      body: { error: 'Greeting not found' },
    }
  }
  
  logger.info('Retrieved greeting', { requestId })
  
  return {
    status: 200,
    body: greeting,
  }
}

Next Steps

Now that you’ve built your first Motia application, explore more advanced features:

Todo API

Build a full CRUD API with validation

Message Queue

Learn pub/sub patterns and streams

Scheduled Tasks

Run jobs on a schedule with cron

API Reference

Explore the full Motia API

Build docs developers (and LLMs) love