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