Documentation
supastarter for Nuxtsupastarter for NuxtOrganizations

Store data for organizations

Learn how to store data for organizations in your supastarter application.

When working with organizations, you most likely want to store data for each organization that can be accessed by the organization members.

We are re-using the post example from the API documentation and extend it to store the organization with it. Let's assume you want to enable all members of an organization to edit the posts of the organization instead of just the author.

You probably still want the individual author to be defined on the post, so you can still see who created the post.

Adjust the database schema

To do this, you can add the organizationId field in the post schema.

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  organizationId String
  organization Organization @relation(fields: [organizationId], references: [id]) 
}

model Organization {
  // ...
  posts Post[]
}

model User {
  // ...
  posts Post[]
}

Add organizationId to the post creation endpoint

To create a post now, you need to pass the organizationId to the endpoint.

packages/api/modules/posts/procedures/create.ts
import { protectedProcedure } from "../../../orpc/procedures";
import { verifyOrganizationMembership } from "@repo/auth";
import { z } from "zod";

export const createPost = protectedProcedure
  .input(z.object({
    title: z.string(),
    content: z.string(),
    organizationId: z.string(),
  }))
  .handler(async ({ input, context: { user } }) => {
    const { title, content, organizationId } = input;

    // verify that the user is a member of the organization
    // will throw an error if the user is not a member
    const { organization, role } = await verifyOrganizationMembership(organizationId, user.id);

    // optionally you can check the role of the user
    // if (role !== "admin") {
    //   throw new ORPCError("FORBIDDEN", { message: "You are not an admin of this organization" });
    // }

    const post = await db.post.create({
      data: {
        title,
        content,
        authorId: user.id,
        organizationId,
      },
    });

    return post;
  });

This procedure is protected by the protectedProcedure which means only authenticated users can access it. Beyond that, we are checking if the user is a member of the organization with the verifyOrganizationMembership helper function. If the user is not a member, the function will throw an error and the request will not be processed further.

Create a post from the UI

To create a post from the UI, you can use the oRPC client with Vue Query:

<script setup lang="ts">
const { activeOrganization } = useActiveOrganization();
const { $orpcClient } = useNuxtApp();

const { mutateAsync: createPost, isPending } = useMutation({
  mutationFn: async (data: { title: string; content: string }) => {
    if (!activeOrganization.value) {
      throw new Error("No active organization found");
    }

    return $orpcClient.posts.create({
      ...data,
      organizationId: activeOrganization.value.id,
    });
  },
});
</script>

Add organizationId to the post query endpoint

Now you probably want to query the posts of an organization to list them in the UI. To do this, you can add the organizationId to the endpoint which lists all posts.

packages/api/modules/posts/procedures/list.ts
import { protectedProcedure } from "../../../orpc/procedures";
import { verifyOrganizationMembership } from "@repo/auth";
import { z } from "zod";

export const listPosts = protectedProcedure
  .input(z.object({
    organizationId: z.string(),
  }))
  .handler(async ({ input, context: { user } }) => {
    const { organizationId } = input;

    // always verify that the user is a member of the organization
    await verifyOrganizationMembership(organizationId, user.id);

    const posts = await db.post.findMany({
      where: {
        organizationId,
      },
    });

    return posts;
  });

Query posts by organization in the UI

Lastly, to query the posts by organization in the UI, you can use the oRPC client with Vue Query:

<script setup lang="ts">
const { activeOrganization } = useActiveOrganization();
const { $orpcClient } = useNuxtApp();

const { data: posts, isPending } = useQuery({
  queryKey: ["organization", activeOrganization.value?.id, "posts"],
  queryFn: () => $orpcClient.posts.list({
    organizationId: activeOrganization.value!.id,
  }),
  enabled: computed(() => !!activeOrganization.value),
});
</script>

<template>
  <div v-if="isPending">Loading...</div>
  <div v-else-if="!posts?.length">No posts found</div>
  <div v-else>
    <div v-for="post in posts" :key="post.id">{{ post.title }}</div>
  </div>
</template>