A minimal starter for building agentic AI web applications with Google’s ADK framework for TypeScript and Next.js.
- Define and run your agents all in TypeScript!
- Unopinionated boilerplate. Based off standard
create-next-app, customise according to your preferences. - Collocation. No separate backend service needed if you're building a Next.js app.
- Flexible model choice. Use Gemini models or other models with an extensible wrapper class API.
Agent Development Kit (ADK) for TypeScript:
an open-source, code-first TypeScript toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control
Try now:
- ADK TypeScript
- Google GenAI SDK
- Next.js
- Tailwind CSS
- Biome
- Drizzle
- Node.js 20.9+
- Package manager (npm, yarn, pnpm, bun)
- Examples here use npm, replace with your own.
- Gemini or Vertex AI API key
- Get a free API key: https://aistudio.google.com/app/apikey.
-
Clone repo (or "Use this template" from GH web)
git clone https://github.com/ekafyi/starter-nextjs-adk-js.git my-app cd my-app -
Install packages
npm install
-
Copy env file and fill
GEMINI_API_KEYwith your credentialscp .env.example .env nano .env
-
Setup local database (default to
local.db)npm run db:push npm run db:seed
-
Run Next.js dev server (default on
localhost:3000)npm run dev
ADK Web: "the built-in developer UI that is integrated with Agent Development Kit for easier agent development and debugging."
Run ADK dev server (default on localhost:8000):
npx adk web src/agents
# or:
npm run adk-web-ui
src
├── agents/ # Agent definitions
├── app/
│ ├── agent/ # Frontend route `/agent`
│ └── api/
│ └── agent/ # API route `/api/agent`
├── db/ # DB schema and client
├── lib/
│ ├── data/ # Mock external data
│ ├── auth.ts # Mock user auth
│ └── countries.ts # Mock external services call
# Everything else is standard app router Next.js.export const rootAgent = new LlmAgent({
name: "countries_agent",
model: "gemini-2.5-flash",
description: "Agent to answer questions about ...",
// ... etc
});const getCapital = new FunctionTool({
name: "get_country_capital",
description: "Retrieves the capital city for a country.",
parameters: z.object({ /* ... zod schema ... */ }),
execute: async ({ country }) => {
return await getMockCapital(country);
},
});
const getFlag = new FunctionTool({
name: "get_country_flag",
// ... etc
});
export const rootAgent = new LlmAgent({
// ...
tools: [getCapital, getFlag],
});Use optional ToolContext to read/write session-scoped state.
- execute: async ({ country }) => {
+ execute: async ({ country }, toolContext?: ToolContext) => {
+ toolContext?.state.set("last_mentioned_country", country);
// ...
},// Initialize runner with the agent
const runner = new InMemoryRunner({
agent: rootAgent,
appName: "sample_app"
});
// ... inside POST route handler ...
const iterator = runner.runAsync({
userId,
sessionId,
newMessage: createUserContent(message),
});Bring your own auth.
- Replace the placeholder (store and check for
"username"cookie) with your own. - Replace imports from lib/auth.
Currently ADK TypeScript has no built-in support for persistence; everything runs in-memory.
Here I use Drizzle and local SQLite file to persist user sessions and events manually.
- DB client and schema
- Implementation
Local vs Remote DB:
-
Local file works out of the box for running locally.
- (optional) Define DB file name through the
DB_FILE_NAMEenv variable (default tolocal.db). npm run db:pushcreates the DB file and apply the schema.- (optional)
npm run db:seedinserts sample user to the DB.
- (optional) Define DB file name through the
-
If deploying this to a serverless hosting service, use a compatible remote database like Turso. Local file will not persist across serverless function invocations.
- Change the
DB_FILE_NAMEenv variable to alibsql://URL for production. - Make sure to seed the remote DB by running
db:seed.
- Change the
MIT