Motia Icon

API Endpoints

Learn how to create HTTP API endpoints with Motia

What You'll Build

A pet management API with these endpoints:

  • POST /pets - Create a new pet
  • GET /pets - List all pets
  • GET /pets/:id - Get a specific pet
  • PUT /pets/:id - Update a pet
  • DELETE /pets/:id - Delete a pet

workbench

Getting Started

Clone the example repository:

git clone https://github.com/MotiaDev/build-your-first-app.git
cd build-your-first-app
git checkout api-endpoints

Install dependencies:

npm install

Start the Workbench:

npm run dev

Your Workbench will be available at http://localhost:3000.


Project Structure

package.json
requirements.txt
types.d.ts

Files like features.json and tutorial.tsx are only for the interactive tutorial and are not part of Motia's project structure.

All code examples in this guide are available in the build-your-first-app repository.

You can follow this guide to learn how to build a REST API with Motia step by step, or you can clone the repository and dive into our Interactive Tutorial to learn by doing directly in the Workbench.

interactive-tutorial


Creating Your First Endpoint

This tutorial focuses on Motia's capabilities to create complete backend system from APIs to Streaming AI agents step-by-step. Here, we're showcasing writing APIs with Motia Steps - For data persistence, we use a simple JSON file store in the examples. In a real application, you would use a database like PostgreSQL, MongoDB, or any other data store of your choice. The complete store implementation is available in the GitHub repository.

Configuration

Every API endpoint has two parts:

Config - Defines when and how the step runs:

PropertyDescription
nameUnique identifier
typeSet to 'api'
pathURL path for the endpoint
methodHTTP method (GET, POST, PUT, DELETE)

Handler - The function that executes your business logic.

View on GitHub:

steps/typescript/create-pet.step.ts
import { ApiRouteConfig, Handlers } from 'motia'
import { z } from 'zod'
import { TSStore } from './ts-store'
 
const createPetSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  species: z.enum(['dog', 'cat', 'bird', 'other']),
  ageMonths: z.number().int().min(0),
})
 
export const config: ApiRouteConfig = {
  name: 'CreatePet',
  type: 'api',
  path: '/pets',
  method: 'POST',
  bodySchema: createPetSchema,
  flows: ['PetManagement'],
}
 
export const handler: Handlers['CreatePet'] = async (req, { logger }) => {
  const data = createPetSchema.parse(req.body)
  
  // In a real application, this would be a database call
  // e.g., await db.pets.create(data)
  const pet = TSStore.create(data)
 
  logger.info('Pet created', { petId: pet.id })
 
  return { status: 201, body: pet }
}

Testing Your API

You can test your endpoints using curl or the Workbench interface.

Using curl

# Create a pet
curl -X POST http://localhost:3000/pets \
  -H "Content-Type: application/json" \
  -d '{"name": "Max", "species": "dog", "ageMonths": 24}'

Using Workbench

You can also test your endpoint directly in the Workbench, which provides an interactive interface to test your API endpoints with real requests and see the responses in real-time:

create-pet


Adding GET Endpoints

List All Pets

View on GitHub:

steps/typescript/get-pets.step.ts
import { ApiRouteConfig, Handlers } from 'motia'
import { TSStore } from './ts-store'
 
export const config: ApiRouteConfig = {
  name: 'GetPets',
  type: 'api',
  path: '/pets',
  method: 'GET',
  flows: ['PetManagement'],
}
 
export const handler: Handlers['GetPets'] = async (req, { logger }) => {
  // In a real application, this would be a database call
  // e.g., const pets = await db.pets.findMany()
  const pets = TSStore.list()
  
  logger.info('Retrieved all pets', { count: pets.length })
  return { status: 200, body: pets }
}

Testing List All Pets

Test with curl:

# List all pets
curl http://localhost:3000/pets

Or use the Workbench interface:

create-pet


Get Single Pet

View on GitHub:

steps/typescript/get-pet.step.ts
import { ApiRouteConfig, Handlers } from 'motia'
import { TSStore } from './ts-store'
 
export const config: ApiRouteConfig = {
  name: 'GetPet',
  type: 'api',
  path: '/pets/:id',
  method: 'GET',
  flows: ['PetManagement'],
}
 
export const handler: Handlers['GetPet'] = async (req, { logger }) => {
  // In a real application, this would be a database call
  // e.g., const pet = await db.pets.findById(req.pathParams.id)
  const pet = TSStore.get(req.pathParams.id)
 
  if (!pet) {
    logger.warn('Pet not found', { id: req.pathParams.id })
    return { status: 404, body: { message: 'Pet not found' } }
  }
 
  return { status: 200, body: pet }
}

Testing tip: When testing GET endpoints with path parameters like /pets/:id, switch to the Params tab (not Body) to enter the ID value.

The :id in the path creates a path parameter accessible via req.pathParams.id.

Testing Get Single Pet

Test with curl:

# Get specific pet (replace 1 with an actual pet ID)
curl http://localhost:3000/pets/1

Or use the Workbench interface:

create-pet


Adding UPDATE Endpoint

View on GitHub:

steps/typescript/update-pet.step.ts
import { ApiRouteConfig, Handlers } from 'motia'
import { z } from 'zod'
import { TSStore } from './ts-store'
 
const updatePetSchema = z.object({
  name: z.string().min(1).optional(),
  status: z.enum(['available', 'pending', 'adopted']).optional(),
  ageMonths: z.number().int().min(0).optional(),
})
 
export const config: ApiRouteConfig = {
  name: 'UpdatePet',
  type: 'api',
  path: '/pets/:id',
  method: 'PUT',
  bodySchema: updatePetSchema,
  flows: ['PetManagement'],
}
 
export const handler: Handlers['UpdatePet'] = async (req, { logger }) => {
  const updates = updatePetSchema.parse(req.body)
  
  // In a real application, this would be a database call
  // e.g., const pet = await db.pets.update(req.pathParams.id, updates)
  const pet = TSStore.update(req.pathParams.id, updates)
 
  if (!pet) {
    return { status: 404, body: { message: 'Pet not found' } }
  }
 
  logger.info('Pet updated', { petId: pet.id })
  return { status: 200, body: pet }
}

Testing Update Pet

Test with curl:

# Update a pet (replace 1 with an actual pet ID)
curl -X PUT http://localhost:3000/pets/1 \
  -H "Content-Type: application/json" \
  -d '{"status": "adopted"}'

Or use the Workbench interface:

create-pet


Adding DELETE Endpoint

View on GitHub:

steps/typescript/delete-pet.step.ts
import { ApiRouteConfig, Handlers } from 'motia'
import { TSStore } from './ts-store'
 
export const config: ApiRouteConfig = {
  name: 'DeletePet',
  type: 'api',
  path: '/pets/:id',
  method: 'DELETE',
  flows: ['PetManagement'],
}
 
export const handler: Handlers['DeletePet'] = async (req, { logger }) => {
  // In a real application, this would be a database call
  // e.g., const deleted = await db.pets.delete(req.pathParams.id)
  const deleted = TSStore.remove(req.pathParams.id)
 
  if (!deleted) {
    return { status: 404, body: { message: 'Pet not found' } }
  }
 
  logger.info('Pet deleted', { petId: req.pathParams.id })
  return { status: 204 }
}

DELETE endpoints return 204 No Content on success.

Testing Delete Pet

Test with curl:

# Delete a pet (replace 1 with an actual pet ID)
curl -X DELETE http://localhost:3000/pets/1

Or use the Workbench interface:

create-pet


As you can see in this example, Motia handles routing, validation, and error handling automatically. With just a few lines of code, you've built a complete REST API with:

  • Automatic routing based on your step configuration
  • Path parameter extraction (/pets/:idreq.pathParams.id)
  • HTTP method handling (GET, POST, PUT, DELETE)
  • Response formatting with proper status codes
  • Built-in error handling and validation

🎉 Congratulations! You've successfully created your first API endpoints with Motia. Your pet store API is now ready to handle all CRUD operations.


What's Next?

You now have a working REST API for your pet store! But a complete backend system needs more than just API endpoints. In the next guide, we'll add background jobs using Event Steps and scheduled tasks with Cron Steps to handle tasks like:

  • SetNextFeedingReminder - Queue jobs that automatically schedule feeding reminders when pets are added or updated
  • Deletion Reaper - Cron jobs that run daily to clean up soft-deleted records and expired data

Let's continue building your complete backend system by adding these background jobs with Event Steps and scheduled tasks with Cron Steps.

Need help? See our Community Resources for questions, examples, and discussions.