Building a Modern Development Platform: Kiota for Multi-Language API Clients ๐ง
- Introduction ๐
- What is Kiota? ๐ค
- Why Use Kiota? ๐ก
- Adding Kiota to Our Weather App ๐ค๏ธ
- Workflow: From TypeSpec to Kiota Client ๐
- When to Regenerate the Client ๐
- Advantages Over Manual Client Code โจ
- Multi-Language Support ๐
- Best Practices ๐ฏ
- Troubleshooting ๐ง
- Whatโs Next ๐ฎ
- Conclusion ๐
๐ป Source Code: The complete code for this post is available in the
aspire-tools-kiotabranch of our GitHub repository.
Introduction ๐
In our TypeSpec post, we defined our Weather API contract and generated an OpenAPI specification. Now we have a machine-readable API description, but how do frontend and backend teams consume it? Should they manually write HTTP client code? Parse JSON responses into TypeScript interfaces by hand? Hope the API contract doesnโt change?
This is where Kiota comes in. Kiota is Microsoftโs next-generation API client generator that produces clean, idiomatic, type-safe client code from your OpenAPI specifications. Instead of manually writing HTTP requests and response parsing, you get a fully typed SDK that feels native to your language of choice.
What is Kiota? ๐ค
Kiota is an OpenAPI-based API client generator that creates lightweight, strongly-typed HTTP clients for multiple programming languages. Unlike traditional SDK generators that produce bloated code with heavy dependencies, Kiota generates minimal, focused clients that only include what you need.
Key Characteristics:
- ๐ฏ Type-Safe: Generated clients are strongly typed based on your OpenAPI spec
- ๐ชถ Minimal Dependencies: Only includes required libraries, not entire HTTP frameworks
- ๐ Multi-Language: Supports C#, TypeScript, Java, Python, Go, PHP, and more
- ๐ Authentication Flexible: Easy to plug in OAuth, API keys, or custom auth providers
- ๐ฆ OpenAPI Native: Works with any OpenAPI 3.0+ specification
- โก Modern Patterns: Native async/await, proper error handling, and cancellation support
How Kiota Differs from Other Generators ๐
Traditional SDK Generators (like Swagger Codegen):
// Heavy dependencies, bloated code
import { DefaultApi, Configuration } from './generated-sdk';
const config = new Configuration({
basePath: 'https://api.example.com',
// Lots of configuration boilerplate
});
const api = new DefaultApi(config);
const response = await api.getWeatherForecast();
Kiota-Generated Client:
// Clean, minimal, idiomatic code
import { AnonymousAuthenticationProvider } from '@microsoft/kiota-abstractions';
import { FetchRequestAdapter } from '@microsoft/kiota-http-fetchlibrary';
import { createWeatherApiClient } from './client';
const authProvider = new AnonymousAuthenticationProvider();
const adapter = new FetchRequestAdapter(authProvider);
adapter.baseUrl = '/api';
const client = createWeatherApiClient(adapter);
const forecasts = await client.weather.get();
The difference? Kiota generates code that feels hand-written, not machine-generated.
Why Use Kiota? ๐ก
1. Contract-First Development ๐
Kiota completes the contract-first workflow we started with TypeSpec:
- ๐ Define API in TypeSpec
- ๐ Generate OpenAPI spec
- ๐ค Generate clients with Kiota
- ๐ Distribute SDKs to consuming teams
Both frontend and backend teams work from the same source of truth. Changes to the API contract are immediately reflected in generated clients.
2. Type Safety Across Languages ๐
Your OpenAPI models become native types:
OpenAPI Spec:
components:
schemas:
WeatherForecast:
type: object
required:
- id
- date
- temperatureC
properties:
id:
type: string
date:
type: string
format: date
temperatureC:
type: integer
Generated TypeScript:
export interface WeatherForecast {
id: string;
date: Date;
temperatureC: number;
temperatureF?: number;
summary?: string;
}
Generated C#:
public class WeatherForecast
{
public required string Id { get; set; }
public required DateOnly Date { get; set; }
public required int TemperatureC { get; set; }
public int? TemperatureF { get; set; }
public string? Summary { get; set; }
}
No manual typing. No drift. Compiler-enforced correctness.
3. Flexible Authentication ๐
Kiota makes authentication pluggable. Whether youโre using:
- ๐ OAuth 2.0 / OpenID Connect
- ๐ซ API Keys
- ๐ Bearer tokens
- ๐ก๏ธ Custom authentication schemes
You can easily configure the authentication provider:
// OAuth example
const authProvider = new MyOAuthProvider(/* config */);
const adapter = new FetchRequestAdapter(authProvider);
const client = createWeatherApiClient(adapter);
4. Minimal Footprint ๐ชถ
Traditional SDK generators create monolithic clients with everything included. Kiota uses a layered approach:
- Abstractions Layer: Core interfaces (minimal)
- HTTP Library: Pluggable (use fetch, axios, etc.)
- Serialization: JSON support (lightweight)
- Generated Code: Only what your API needs
Result: Much smaller bundle sizes for web applications.
5. Language-Native Patterns ๐
Kiota generates code that follows each languageโs best practices:
TypeScript/JavaScript:
- Promise-based async
- Proper error handling
- Tree-shakeable imports
C#:
- async/await patterns
- IAsyncEnumerable for collections
- Nullable reference types
- CancellationToken support
Python:
- Type hints
- Async/await with asyncio
- Context managers
Java:
- CompletableFuture
- Stream API
- Optional types
Adding Kiota to Our Weather App ๐ค๏ธ
Letโs add Kiota client generation to our React frontend from the Aspire series. Weโll generate a TypeScript client from the OpenAPI spec we created with TypeSpec.
Prerequisites ๐ฆ
From our previous posts, we already have:
- ๐ค๏ธ Weather API with TypeSpec contract
- ๐ Generated OpenAPI specification
- โ๏ธ React TypeScript frontend
- ๐ง Node.js installed in our dev container
Step 1: Install Kiota CLI ๐ ๏ธ
Install Kiota as a .NET global tool:
dotnet tool install -g Microsoft.OpenApi.Kiota
Note: This requires .NET 8.0 SDK to be installed. If youโre working in a dev container or have .NET already installed for your backend development, this is the recommended approach.
Verify the installation:
kiota --version
Step 2: Add Kiota Generation Script to package.json ๐
Navigate to your React app directory (src/WeatherApp.Web) and update package.json to add a script for generating the Kiota client:
{
"name": "weatherapp.web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"generate-client": "kiota generate -l typescript -c WeatherApiClient -n client -d ../generated/openapi/openapi.yaml -o ./src/client"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@microsoft/kiota-bundle": "^1.0.0-preview.99"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"typescript": "~5.6.2",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10"
}
}
Letโs break down the generate-client command: ๐
kiota generate \
-l typescript \ # Language: TypeScript
-c WeatherApiClient \ # Client class name
-n client \ # Namespace/module name
-d ../generated/openapi/openapi.yaml \ # OpenAPI spec path (from TypeSpec emitter)
-o ./src/client # Output directory for generated files
Parameters explained:
- ๐ค
-l typescript: Generate TypeScript client - ๐ท๏ธ
-c WeatherApiClient: Name of the main client class - ๐
-n client: Namespace/module name for generated code (must conform to^[\w][\w\._-]+) - ๐
-d ../generated/openapi/openapi.yaml: Path to OpenAPI spec (from TypeSpecโsemitter-output-dirconfiguration) - ๐
-o ./src/client: Where to write generated files
Step 3: Install Kiota Dependencies ๐ฆ
The generated client will need Kiotaโs runtime libraries. Install the bundle package which includes all necessary dependencies:
# From WeatherApp.Web directory
npm install @microsoft/kiota-bundle
The @microsoft/kiota-bundle package includes:
- ๐ฏ @microsoft/kiota-abstractions: Core abstractions and interfaces
- ๐ @microsoft/kiota-http-fetchlibrary: HTTP adapter using browserโs fetch API
- ๐ @microsoft/kiota-serialization-json: JSON serialization/deserialization
This makes setup simple with just one dependency to manage.
Step 4: Generate the Client ๐ค
Now run the generation script:
npm run generate-client
This will create a src/client/ directory with your generated TypeScript client:
src/client/
โโโ index.ts # Main exports
โโโ weatherApiClient.ts # Main client class
โโโ models/ # Generated model types
โ โโโ weatherForecast.ts
โ โโโ index.ts
โโโ weather/ # API endpoint methods
โโโ index.ts
โโโ weatherRequestBuilder.ts
Step 5: Configure the Client in Your React App ๐ง
Create a client configuration file src/services/weatherService.ts:
import { AnonymousAuthenticationProvider } from '@microsoft/kiota-abstractions';
import { FetchRequestAdapter } from '@microsoft/kiota-http-fetchlibrary';
import { createWeatherApiClient } from '../client/weatherApiClient.ts';
// Create an anonymous authentication provider (no auth required)
const authProvider = new AnonymousAuthenticationProvider();
// Create the HTTP adapter with the auth provider
const adapter = new FetchRequestAdapter(authProvider);
// Set the base URL for your API
// Uses '/api' to leverage Vite's proxy configuration for local development
adapter.baseUrl = '/api';
// Create and export the configured client
export const weatherApiClient = createWeatherApiClient(adapter);
Note: Weโre using
AnonymousAuthenticationProvidersince our Weather API doesnโt require authentication. For APIs that require authentication (OAuth, API keys, etc.), you would use a different authentication provider. Weโre also using/apias the base URL to take advantage of Viteโs proxy configuration.
Step 6: Use the Generated Client in React ๐ป
Now update your React component to use the Kiota-generated client instead of manual fetch calls:
Before (Manual Fetch):
const [forecasts, setForecasts] = useState<WeatherForecast[]>([]);
useEffect(() => {
fetch('/api/weatherforecast')
.then(res => res.json())
.then(data => setForecasts(data));
}, []);
After (Kiota Client):
import { useState, useEffect } from 'react';
import { weatherApiClient } from './services/weatherService.ts';
import type { WeatherForecast } from './client/models/index.ts';
function App() {
const [forecasts, setForecasts] = useState<WeatherForecast[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadForecasts = async () => {
try {
const data = await weatherApiClient.weatherforecast.get();
if (data) {
setForecasts(data);
setLoading(false);
}
} catch (error) {
console.error('Failed to load weather forecasts:', error);
setError('Failed to load weather forecasts');
setLoading(false);
}
};
loadForecasts();
}, []);
if (loading) {
return <div className="loading">Loading weather data...</div>;
}
if (error) {
return (
<div className="error">
<h2>Error</h2>
<p>{error}</p>
<button onClick={() => window.location.reload()}>Retry</button>
</div>
);
}
return (
<div className="weather-app">
<h1>Weather Forecast</h1>
{/* Render your weather forecast data */}
<ul>
{forecasts.map((forecast, index) => (
<li key={index}>{/* forecast data */}</li>
))}
</ul>
</div>
);
}
โ ๏ธ Important Note about
additionalData: Depending on your OpenAPI schema and how Kiota generates the models, you may find that properties are stored in anadditionalDatadictionary rather than as direct properties on the model. If you encounter this, you can access the data like this:const data = forecast.additionalData as any; const temperature = data?.TemperatureC; const summary = data?.Summary;This is a known behavior when the OpenAPI schema doesnโt have a strict object definition or when using certain serialization settings. You can work around this by refining your TypeSpec schema to ensure properties are properly typed, or by creating a wrapper type for better type safety.
Benefits of the Kiota approach: โ
- โจ Full TypeScript IntelliSense for API methods and models
- ๐ Type safety with generated
WeatherForecastinterface - ๐ฏ Autocomplete for API endpoints (
weatherApiClient.weatherforecast.get()) - โ ๏ธ Compile-time errors if API contract changes
- ๐งน Cleaner, more maintainable code with proper error handling
Workflow: From TypeSpec to Kiota Client ๐
Hereโs the complete workflow weโve built:
1. Define API Contract (TypeSpec)
โ
2. Generate OpenAPI Spec
โ
3. Generate Kiota Client
โ
4. Use in Application
Commands summary:
# 1. Generate OpenAPI from TypeSpec
cd WeatherApp.Typespec
tsp compile .
# 2. Generate Kiota client for React
cd ../src/WeatherApp.Web
npm run generate-client
# 3. Run your app with Aspire (requires Aspire CLI installed)
aspire run
Note:
aspire runstarts the entire application including the backend API and frontend, orchestrated by .NET Aspire. This requires the .NET Aspire workload to be installed.
When to Regenerate the Client ๐
You should regenerate your Kiota client whenever:
- ๐ API Contract Changes: TypeSpec models or operations are modified
- ๐ง OpenAPI Spec Updates: New endpoints or parameters added
- ๐ Bug Fixes: Issues in the OpenAPI specification are corrected
- โฌ๏ธ Breaking Changes: Major version updates to your API
Automation Tip: ๐ก Add the client generation to your CI/CD pipeline:
# Azure DevOps example
- script: |
npm run generate-client
displayName: 'Generate Kiota Client'
workingDirectory: src/WeatherApp.Web
Advantages Over Manual Client Code โจ
Type Safety
- โ Compiler catches API contract mismatches
- โ IntelliSense shows available operations
- โ Refactoring tools work correctly
Consistency
- โ Same patterns across all API calls
- โ Uniform error handling
- โ Consistent authentication
Maintainability
- โ One command to update client when API changes
- โ No manual JSON parsing code
- โ Less boilerplate
Developer Experience
- โ Discover APIs through autocomplete
- โ Self-documenting code
- โ Faster development
Multi-Language Support ๐
While we used TypeScript for our React app, Kiota can generate clients for:
Supported Languages:
- ๐ฆ TypeScript/JavaScript: Browser and Node.js
- ๐ช C#: .NET applications
- โ Java: Spring Boot, Android
- ๐ Python: Django, Flask, FastAPI
- ๐ท Go: Backend services
- ๐ PHP: Laravel, WordPress
- ๐ Ruby: Rails applications
Same OpenAPI spec, multiple clients:
# Generate C# client for backend services
kiota generate -l csharp -c WeatherClient -n MyApp.Clients -d openapi.yaml -o ./clients/csharp
# Generate Python client for data science teams
kiota generate -l python -c WeatherClient -n weather_client -d openapi.yaml -o ./clients/python
# Generate Java client for Android app
kiota generate -l java -c WeatherClient -n com.myapp.clients -d openapi.yaml -o ./clients/java
Best Practices ๐ฏ
1. Version Your Generated Code ๐
Option A: Check in generated code
- โ Pros: Reviewable diffs, no build dependency
- โ Cons: Merge conflicts, larger repo
Option B: Generate on build
- โ Pros: Always up-to-date, smaller repo
- โ Cons: Build-time dependency, potential breakage
Recommendation: Check in generated code initially, regenerate when spec changes.
2. Separate Client Configuration ๐ง
Keep API client setup separate from business logic:
// src/services/apiClient.ts - Configuration
export const weatherApiClient = createWeatherApiClient(adapter);
// src/hooks/useWeather.ts - Business logic
export function useWeather() {
return useQuery({
queryKey: ['weather'],
queryFn: () => weatherApiClient.weather.get()
});
}
3. Add Error Handling ๐ก๏ธ
Wrap client calls with proper error handling:
async function getForecasts() {
try {
const forecasts = await weatherApiClient.weather.get();
return { data: forecasts, error: null };
} catch (error) {
console.error('API Error:', error);
return { data: null, error: error as Error };
}
}
4. Use Environment Variables ๐
Donโt hardcode API URLs:
// .env.development
VITE_API_URL=http://localhost:5000
// .env.production
VITE_API_URL=https://api.production.com
// apiClient.ts
adapter.baseUrl = import.meta.env.VITE_API_URL;
Troubleshooting ๐ง
Issue: โModule not foundโ after generation
Solution: Make sure the Kiota bundle package is installed:
npm install @microsoft/kiota-bundle
Issue: Generated code has type errors
Solution: Ensure TypeScript version compatibility:
npm install -D typescript@latest
Issue: API calls fail with CORS errors
Solution: Configure CORS in your API (already done in our Aspire setup):
builder.Services.AddCors(options =>
Issue: โModule not foundโ after generation
Solution: Make sure the Kiota bundle package is installed:
npm install @microsoft/kiota-bundle
Issue: Generated code has type errors
Solution: Ensure TypeScript version compatibility:
npm install -D typescript@latest
Issue: API calls fail with CORS errors
Solution: Configure CORS in your API (already done in our Aspire setup):
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
Whatโs Next ๐ฎ
In upcoming posts, weโll explore:
- ๐๏ธ Terraform patterns for Azure infrastructure
- ๐ฆ Creating custom .NET templates
- ๐ Azure DevOps pipeline setup
- ๐ญ Service virtualization with WireMock
Each tool builds on the previous ones, creating a cohesive modern development platform.
Conclusion ๐
Kiota completes our contract-first development workflow:
- โ TypeSpec defines the API contract
- โ OpenAPI spec is generated
- โ Kiota creates type-safe clients
- โ Frontend/backend teams work in parallel
The result? Faster development, fewer bugs, and happier developers.
No more manual JSON parsing. No more keeping TypeScript interfaces in sync with C# models. No more โthe API changed and broke everythingโ surprises.
With Kiota, your API contract is your source of truth, and changes propagate automatically to all consumers.
Try it out: Add Kiota to your next API project and experience the difference contract-first development makes!
Resources: ๐
More
Recent Posts
- » Building a Modern Development Platform: Kiota for Multi-Language API Clients ๐ง
- » Building a Modern Development Platform: Deploying Platform Documentation with Azure Storage and Front Door ๐
- » Building a Modern Development Platform: Terraform & Terraform Cloud for Azure Infrastructure ๐๏ธ
- » Building a Modern Development Platform: TypeSpec for Contract-First API Development ๐
- » Building a Modern Development Platform: Aspire for Local Development