Integrate Mastra in your Astro project
In this guide, you'll build a tool-calling AI agent using Mastra, then connect it to Astro by importing and calling the agent directly from your routes.
You'll use AI SDK UI and AI Elements to create a beautiful, interactive chat experience.
While this guide shows you how to use Astro with React and full server-side rendering (SSR), there are many ways to use Astro with Mastra. You can opt-in to SSR on a per-file basis and use other frameworks like Svelte, Vue, Solid, or Preact. You can use Astro to build out a chat interface and call endpoints natively in Astro.
Before you beginDirect link to Before you begin
- You'll need an API key from a supported model provider. If you don't have a preference, use OpenAI.
- Install Node.js
v22.13.0or later
Create a new Astro app (optional)Direct link to Create a new Astro app (optional)
If you already have an Astro app, skip to the next step. Your Astro app should be setup like this:
- Use SSR (
output: "server"inastro.config.mjs) - Use the React integration
- Tailwind installed
To support on-demand rendering this guide will use the Node.js adapter but any of the supported server adapters will work.
- npm
- pnpm
- yarn
- bun
npm create astro@latest mastra-astro -- --add node --add react --add tailwind --install --skip-houston --template minimal --git
pnpm create astro@latest mastra-astro --add node --add react --add tailwind --install --skip-houston --template minimal --git
yarn create astro@latest mastra-astro --add node --add react --add tailwind --install --skip-houston --template minimal --git
bun create astro@latest mastra-astro --add node --add react --add tailwind --install --skip-houston --template minimal --git
This creates a project called mastra-astro, but you can replace it with any name you want.
cd into your project and edit the astro.config.mjs file to set the output setting to "server":
// @ts-check
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import react from '@astrojs/react';
import tailwindcss from '@tailwindcss/vite';
// https://astro.build/config
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone'
}),
integrations: [react()],
vite: {
plugins: [tailwindcss()]
}
});
Lastly, edit the tsconfig.json to resolve paths:
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
// ...
}
}
Initialize MastraDirect link to Initialize Mastra
Run mastra init. When prompted, choose a provider (e.g. OpenAI) and enter your key:
- npm
- pnpm
- yarn
- bun
npx mastra@latest init
pnpm dlx mastra@latest init
yarn dlx mastra@latest init
bunx mastra@latest init
This creates a src/mastra folder with an example weather agent and the following files:
index.ts- Mastra config, including memorytools/weather-tool.ts- a tool to fetch weather for a given locationagents/weather-agent.ts- a weather agent with a prompt that uses the tool
You'll call weather-agent.ts from your Astro routes in the next steps.
Install AI SDK UI & AI ElementsDirect link to Install AI SDK UI & AI Elements
Install AI SDK UI along with the Mastra adapter:
- npm
- pnpm
- yarn
- bun
npm install @mastra/ai-sdk@latest @ai-sdk/react ai
pnpm add @mastra/ai-sdk@latest @ai-sdk/react ai
yarn add @mastra/ai-sdk@latest @ai-sdk/react ai
bun add @mastra/ai-sdk@latest @ai-sdk/react ai
Next, initialize AI Elements. When prompted, choose the default options:
- npm
- pnpm
- yarn
- bun
npx ai-elements@latest
pnpm dlx ai-elements@latest
yarn dlx ai-elements@latest
bunx ai-elements@latest
This downloads the entire AI Elements UI component library into a @/components/ai-elements folder.
Create a chat routeDirect link to Create a chat route
Create src/pages/api/chat.ts:
import type { APIRoute } from "astro";
import { handleChatStream } from '@mastra/ai-sdk';
import { toAISdkV5Messages } from '@mastra/ai-sdk/ui'
import { createUIMessageStreamResponse } from 'ai';
import { mastra } from '@/mastra';
const THREAD_ID = 'example-user-id';
const RESOURCE_ID = 'weather-chat';
export const POST: APIRoute = async ({ request }) => {
const params = await request.json();
const stream = await handleChatStream({
mastra,
agentId: 'weather-agent',
params: {
...params,
memory: {
...params.memory,
thread: THREAD_ID,
resource: RESOURCE_ID,
}
}
})
return createUIMessageStreamResponse({ stream })
}
export const GET: APIRoute = async () => {
const memory = await mastra.getAgentById('weather-agent').getMemory()
let response = null
try {
response = await memory?.recall({
threadId: THREAD_ID,
resourceId: RESOURCE_ID,
})
} catch {
console.log('No previous messages found.')
}
const uiMessages = toAISdkV5Messages(response?.messages || []);
return Response.json(uiMessages)
}
The POST route accepts a prompt and streams the agent's response back in AI SDK format, while the GET route fetches message history from memory so the UI can be hydrated when the client reloads.
Create a chat componentDirect link to Create a chat component
Create src/components/chat.tsx:
import '@/styles/global.css';
import { useEffect, useState } from 'react';
import { DefaultChatTransport, type ToolUIPart } from 'ai';
import { useChat } from '@ai-sdk/react';
import {
PromptInput,
PromptInputBody,
PromptInputTextarea,
} from '@/components/ai-elements/prompt-input';
import {
Conversation,
ConversationContent,
ConversationScrollButton,
} from '@/components/ai-elements/conversation';
import { Message, MessageContent, MessageResponse } from '@/components/ai-elements/message';
import {
Tool,
ToolHeader,
ToolContent,
ToolInput,
ToolOutput,
} from '@/components/ai-elements/tool';
function Chat() {
const [input, setInput] = useState<string>('');
const { messages, setMessages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
});
useEffect(() => {
const fetchMessages = async () => {
const res = await fetch('/api/chat');
const data = await res.json();
setMessages([...data]);
};
fetchMessages();
}, [setMessages]);
const handleSubmit = async () => {
if (!input.trim()) return;
sendMessage({ text: input });
setInput('');
};
return (
<div className="w-full p-6 relative size-full h-screen">
<div className="flex flex-col h-full">
<Conversation className="h-full">
<ConversationContent>
{messages.map((message) => (
<div key={message.id}>
{message.parts?.map((part, i) => {
if (part.type === 'text') {
return (
<Message
key={`${message.id}-${i}`}
from={message.role}>
<MessageContent>
<MessageResponse>{part.text}</MessageResponse>
</MessageContent>
</Message>
);
}
if (part.type?.startsWith('tool-')) {
return (
<Tool key={`${message.id}-${i}`}>
<ToolHeader
type={(part as ToolUIPart).type}
state={(part as ToolUIPart).state || 'output-available'}
className="cursor-pointer"
/>
<ToolContent>
<ToolInput input={(part as ToolUIPart).input || {}} />
<ToolOutput
output={(part as ToolUIPart).output}
errorText={(part as ToolUIPart).errorText}
/>
</ToolContent>
</Tool>
);
}
return null;
})}
</div>
))}
<ConversationScrollButton />
</ConversationContent>
</Conversation>
<PromptInput onSubmit={handleSubmit} className="mt-20">
<PromptInputBody>
<PromptInputTextarea
onChange={(e) => setInput(e.target.value)}
className="md:leading-10"
value={input}
placeholder="Type your message..."
disabled={status !== 'ready'}
/>
</PromptInputBody>
</PromptInput>
</div>
</div>
);
}
export default Chat;
This component connects useChat() to the api/chat endpoint, sending prompts there and streaming the response back in chunks.
It renders the response text using the <MessageResponse> component, and shows any tool invocations with the <Tool> component.
Render the chat componentDirect link to Render the chat component
The last step is to render the chat component on your index page. Edit src/pages/index.astro:
---
import Chat from '@/components/chat';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<Chat client:load />
</body>
</html>
Import the Chat component and add it to the body with the client:load directive so it runs on the client side.
Test your agentDirect link to Test your agent
- Run your Astro app with
npm run dev - Open the chat at http://localhost:4321
- Try asking about the weather. If your API key is set up correctly, you'll get a response
Next stepsDirect link to Next steps
Congratulations on building your Mastra agent with Astro! 🎉
From here, you can extend the project with your own tools and logic:
When you're ready, read more about how Mastra integrates with AI SDK UI and how to deploy your agent anywhere: