API first with TypeSpec, OpenAPI, React/Tanstack and Microsoft Dynamics 365 Business Central, part 2
DEC 14, 2025
This is the part 2 of the "API first" article series about TypeSpec, OpenAPI, React/Tanstack and Microsoft Dynamics 365 Business Central. Please ensure you have read part 1 before you go on with this article.
Setting up the frontend application
To use the Sales Order TypeSpec and OpenAPI definition, we need to set up our frontend application. A good starting point here is to use Tanstack Query with openapi-client-axios. Let's dive in. In your root of <project-name> folder, let's run:
npm create @tanstack/start@latest
Instead of setting up the folder structure manually, fork and clone the complete project here: github.com/sn4h/vite-tanstack-typespec-openapi
If you haven't used TanStack before, this is your chance! It's a suite of headless, type-safe libraries for managing asynchronous state.
Installing dependencies
Next, let's install the openapi-client-axios and the openapicmd:
npm install --save axios openapi-client-axios
npm install --save-dev openapicmd
To our package we now need to specify a command to build the types based on the openapi.yaml:
"api:build": "npm run tsp:compile && npx openapi typegen ./public/definition/openapi.yaml > src/@types/openapi.d.ts"
Configuring Axios for type safety
Next, to make Axios type safe we need to define the OpenAPIClientAxios and pass in the openapi definition:
import { OpenAPIClientAxios } from 'openapi-client-axios'
import type { Client } from '@/@types/openapi'
const api = new OpenAPIClientAxios({
definition: '/definition/openapi.yaml',
})
let clientInstance: Client | null = null
export const getAxiosClient = async (): Promise<Client> => {
if (!clientInstance) {
clientInstance = await api.getClient<Client>()
}
return clientInstance
}
Using the client in routes
And in our index route in the routes folder we can now use this client to query our SalesOrders:
import { createFileRoute } from '@tanstack/react-router'
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'
import { getAxiosClient } from '@/lib/axios-client.ts'
import { SalesOrderResponse } from '@/@types/openapi'
export const SALES_ORDER_QUERY_KEY = 'salesOrder'
export const fetchSalesOrders = async () => {
const client = await getAxiosClient()
const response = await client.SalesOrder()
return response.data as SalesOrderResponse
}
const salesOrderQueryOptions = queryOptions({
queryKey: [SALES_ORDER_QUERY_KEY],
queryFn: () => fetchSalesOrders(),
})
export const Route = createFileRoute('/')({
loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(salesOrderQueryOptions),
component: App,
})
function App() {
const { data } = useSuspenseQuery(salesOrderQueryOptions)
return (
<div className="p-4">
<header>Hello, SalesOrders!</header>
<article>
<ul>
{data.value.map((order) => (
<li key={order.id}>{order.number}</li>
))}
</ul>
</article>
</div>
)
}
Conclusion
That's it, end-to-end type safety with TypeSpec, OpenAPI, Axios and Tanstack.
Full source of this example is found here:
Thanks for reading