Nuxt
API

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:

published-posts.ts
import { z } from 'zod';
import { publicProcedure } from '../../trpc';
import { db, PostSchema } from 'database';
 
export const publishedPosts = publicProcedure.output(z.array(PostSchema)).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:

index.ts
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:

router.ts
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().

index.vue
<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:

index.vue
<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():

index.vue
<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>