API
For the API layer supastarter integrates tRPC (opens in a new tab) with tRPC Nuxt (opens in a new tab).
Why tRPC:
tRPC is a modern RPC framework for TypeScript and gives you a way to define your API in a type-safe way. It also has a
lot of features like caching, batching, authorization and more. It also has a wide range of extensions like tRPC OpenAPI (opens in a new tab)
which you can use to generate an OpenAPI endpoint for your API.
Defining your API
All API endpoints are defined in the /packages/api/
library. In here you will find a modules
folder which contains
the different features modules of the API.
Create a new API endpoint
To create a new API endpoint you can either create a new module or add a new endpoint to an existing module.
Create a new module
To create a new module you can create a new folder in the modules
folder. For example modules/posts
. In this folder create a new sub-folder /procedures
with an index.ts
file.
Then create a new .ts file for the endpoint in the /procedures
folder. For example modules/posts/procedures/published-posts.ts
:
import { z } from 'zod';
import { publicProcedure } from '../../trpc';
import { db, PostModel } from 'database';
export const publishedPosts = publicProcedure.output(z.array(PostModel)).query(async () => {
const posts = await db.post.findMany({
where: {
published: true,
},
});
return posts;
});
Export endpoint from module
To export the endpoint from the module you need to add it to the /procedures/index.ts
file of the module:
export * from './published-posts';
Register module router
To make the module and it's endpoints available in the API you need to register a router for this module in the /modules/trpc/router.ts
file:
import * as postsProcedures from '../posts/procedures';
export const apiRouter = router({
// ...
posts: router(postsProcedures),
});
Use endpoint in frontend
You can use the endpoints with the help of the auto-imported composable useApiCaller()
. This composable exposes a tRPC Nuxt Client (opens in a new tab).
Using a data loading composable
If you want to have features like caching, pending, errors, etc., you can use the .useQuery()
or .useLazyQuery()
methods, which are built on top of Nuxt useAsyncData
(opens in a new tab).
This only works inside <script setup>
. If you want to call your endpoints inside utils, composables, or anywhere else,
use .query()
.
<script setup lang="ts">
const { apiCaller } = useApiCaller();
const { data: plans } = await apiCaller.billing.plans.useQuery();
</script>
<template>
<!-- do something with the data... -->
<div>{{ plans }}</div>
</template>
If you want to load the data lazily (meaning that you will have to handle the loading state), you can do that like so:
<script setup lang="ts">
const { apiCaller } = useApiCaller();
const { data: plans, pending } = apiCaller.billing.plans.useLazyQuery();
</script>
<template>
<div v-if="pending">Loading...</div>
<!-- do something with the data... -->
<div v-else>{{ plans }}</div>
</template>
Using the API caller directly
If you want to call your endpoints inside utils, composables, or anywhere else, use .query()
:
<script setup lang="ts">
const { apiCaller } = useApiCaller();
// This could be a util or composable in another file.
const getPlans = async () => {
// Other stuff...
return await apiCaller.billing.plans.query();
};
onMounted(async () => {
const plans = await getPlans();
// do something with the data...
console.log({ plans });
});
</script>
<template>
<div />
</template>