Nextjs example
Since Next is a React framework, it’s going to share a lot of common ground with the React examples.
That being said, there are some common patterns in Next which slightly differ from the vanilla react experience.
We will mirror each possible concept in the
Data Fetching
Server
We will use the app router version of nextjs routing, since it’s the premier way to work with server actions and components.
fetch
Next.js extends the native fetch
web API what some of their own primitives for caching and revalidation.
Since @hulla/api
delegates the data-fetching to the frameworks and is not opinionated about fetching logic, you can define your procedure just fine.
import { api } from '@hulla/api'
const a = api()
export const exampleAPI = a.router({
name: 'example',
routes: [
a.procedure('example', () =>
fetch('https://api.com/example', { cache: 'force-cache' })
),
]
})
or with request integration
routes: [
a.request.get('example', {
url: 'https://api.com/example',
cache: 'force-cache',
}),
]
and then just call it on server
import { exampleAPI } from '@/api/example'
async function Page() {
const data = await exampleAPI.call('example')
// ...
}
Server actions
Similar to the server fetch, you can define your server actions.
async function example() {
'use server'
return exampleAPI.call('example')
}
Wether you define your server actions inside a server component or a separate actions.ts
makes no difference, both approach work.
Third party libraries
These obviously depend on which library you use and how server compatible it is. Just keep in mind @hulla/api
just provides a way to organize your calls
and does not override anything.
In case you have something like your database client, you can use it as normal
routes: [a.procedure('getUserById', (id: string) => db.users.get(id))]
or the react cache API
import { cache } from 'react'
export const getUserById = cache(async (id: string) => {
const user = await db.users.get(id)
return user
})
// then in router definition
routes: [a.procedure('getUserById', getUserById)]
and then you can use the route segment config options for cache/revalidation options on specific route segment.
export const dynamic = 'auto'
export const fetchCache = 'auto'
export const revalidate = 'false'
export default async function Page() {
await usersAPI.call('getUserById', '1')
}
Client
While you should ideally fetch what you can on server, there are some times when you do need to fetch some data on the client.
Route handlers
You can also call your API procedures in route handlers
This technically blurs the boundary between client and server, since the route itself runs on server and hydrates data to the client, but at least the official Nextjs docs categorizes this pattern under client side fetching.
export async function GET() {
const res = exampleAPI.call('example')
const data = await res.json()
return Response.json({ data })
}
@tanstack/query
All the common client-side fetching patterns will work. The quickest way to transform your API calls to a useQuery
query options,
is with the official @tanstack/query integration.
import { api } from '@hulla/api'
import { query } from '@hulla/api-query'
import { db } from '../db'
const a = api()
export const usersAPI = a.router({
name: 'users',
routes: [a.procedure('getAllUsers', () => db.users.get())],
adapters: { query }, // added the query adapter here
})
and then
import { useQuery } from '@tanstack/react-query'
import { usersAPI } from '@/api/users'
export function ClientComponent() {
const { data } = useQuery(usersAPI.query.call('getAllusers'))
}
Note: @tanstack/query also has an experimental server hydration API, that mainly uses
prefetchQuery
(and some additional configuration). This should still be 100% compatible, check out the official docs
swr
Similar to @tanstack/query
integration above, swr
is an alternative for data fetching. We will use the recommended
swr integration to transform our procedures to useSWR
arguments.
import { api } from '@hulla/api'
import { swr } from '@hulla/api-swr'
import { db } from '../db'
const a = api()
export const usersAPI = a.router({
name: 'users',
routes: [a.procedure('getAllUsers', () => db.users.get())],
adapters: { swr }, // added the query adapter here
})
and then
import useSWR from 'swr'
import { usersAPI } from '@/api/users'
export function ClientComponent() {
const { data } = useSWR(...usersAPI.swr.call('getAllusers'))
}
Demo
Here’s a quick interactive demo you can play around with: