Handling Relations
Arkos provides an easier way to interact with Prisma relations through its Arkos Prisma Input system — a built-in runtime transformation that automatically converts flattened JSON data into proper Prisma operations like connect, create, and update. This means you can send intuitive data structures from your frontend without manually structuring nested relation objects.
For type safety in your custom code, the ArkosPrismaInput TypeScript utility type is also available, giving you the same flattened format with full type inference.
How It Works
Arkos scans relation fields in your request body and converts them based on the data shape. The exact result differs slightly depending on whether the relation is single (one-to-one) or array (one-to-many) — most notably for delete and disconnect.
Single (one-to-one) relations
| Input Pattern | Operation | Result |
|---|---|---|
{ id: 5 } | Connect | { connect: { id: 5 } } |
{ name: "New Item" } | Create | { create: { name: "..." } } |
{ id: 5, name: "Updated" } | Update | { update: { where: { id: 5 }, data: { name: "..." } } } |
{ apiAction: "delete" } | Delete | { delete: true } |
{ apiAction: "disconnect" } | Disconnect | { disconnect: true } |
For single relations, delete and disconnect don't need an id — there's only
one possible related record, so Arkos always resolves them to { delete: true }
/ { disconnect: true }. This is different from array relations below, where an
id is required to target the specific item to remove.
Array (one-to-many) relations
| Input Pattern | Operation | Result |
|---|---|---|
{ id: 5 } | Connect | { connect: { id: 5 } } |
{ name: "New Item" } | Create | { create: { name: "..." } } |
{ id: 5, name: "Updated" } | Update | { update: { where: { id: 5 }, data: { name: "..." } } } |
{ id: 5, apiAction: "delete" } | Delete | { deleteMany: { id: { in: [5] } } } |
{ id: 5, apiAction: "disconnect" } | Disconnect | { disconnect: [{ id: 5 }] } |
Setup
No configuration needed. Arkos automatically handles relation fields in all auto-generated endpoints. For custom code, you can use the ArkosPrismaInput type for type-safe flattened inputs:
import { ArkosPrismaInput } from "arkos/prisma";
import { Prisma } from "@prisma/client";
type CreatePostInput = ArkosPrismaInput<Prisma.PostCreateInput>;
const postData: CreatePostInput = {
title: "My Post",
author: { id: 1 }, // Auto-converts to connect
tags: [
{ name: "Technology" }, // Auto-converts to create
{ id: 5 } // Auto-converts to connect
]
};ArkosPrismaInput utility type is available since v1.5.0-beta. For earlier versions, Arkos still handles relations automatically at runtime — this type just adds TypeScript safety.
Examples
Single (One-to-One) Relations
The same flattened input works for one-to-one relations. Below is every operation
against a single profile relation on User:
import { ArkosPrismaInput } from "arkos/prisma";
import { Prisma } from "@prisma/client";
// Connect to an existing profile
const connectProfile: ArkosPrismaInput<Prisma.UserUpdateInput> = {
profile: { id: 42 },
};
// -> profile: { connect: { id: 42 } }
// Create a new profile inline
const createProfile: ArkosPrismaInput<Prisma.UserUpdateInput> = {
profile: { bio: "Full-stack developer" },
};
// -> profile: { create: { bio: "Full-stack developer" } }
// Update the connected profile in place
const updateProfile: ArkosPrismaInput<Prisma.UserUpdateInput> = {
profile: { id: 42, bio: "Updated bio" },
};
// -> profile: { update: { where: { id: 42 }, data: { bio: "Updated bio" } } }
// Delete the connected profile — no id needed
const deleteProfile: ArkosPrismaInput<Prisma.UserUpdateInput> = {
profile: { apiAction: "delete" },
};
// -> profile: { delete: true }
// Disconnect without deleting — no id needed
const disconnectProfile: ArkosPrismaInput<Prisma.UserUpdateInput> = {
profile: { apiAction: "disconnect" },
};
// -> profile: { disconnect: true }Create with Mixed Relations
const createPost: ArkosPrismaInput<Prisma.PostCreateInput> = {
title: "New Blog Post",
content: "This is the content",
author: { id: 123 }, // Connect to existing user (single relation)
tags: [
{ name: "Technology" }, // Create new tag
{ name: "Programming" }, // Create new tag
{ id: 5 } // Connect existing tag
]
};Update with Mixed Operations
const updatePost: ArkosPrismaInput<Prisma.PostUpdateInput> = {
title: "Updated Title",
comments: [
{ id: 1, content: "Updated comment" }, // Update existing
{ content: "New comment" }, // Create new
{ id: 3, apiAction: "delete" } // Delete — id required for array relations
]
};Connect by Unique Fields
const createOrder: ArkosPrismaInput<Prisma.OrderCreateInput> = {
total: 99.99,
customer: { email: "customer@example.com" }, // Connect by email (must be @unique), single relation
products: [
{ sku: "PROD-123" }, // Connect by SKU (must be @unique)
{ sku: "PROD-456" }
]
};Nested Relations
Arkos handles nested relations recursively, and single and array relations can be nested inside one another at any depth:
const createPost: ArkosPrismaInput<Prisma.PostCreateInput> = {
title: "My Post",
author: { id: 1 }, // Single relation on the post itself
comments: [
{
content: "Great post!",
author: { id: 2 } // Single relation nested inside an array relation — connect
},
{
content: "Thanks",
author: {
name: "Anonymous", // Single relation nested inside an array relation — create
email: "anon@example.com"
}
}
]
};Explicit Operations with apiAction
For ambiguous cases, use apiAction to specify the operation. The id requirement
differs by relation shape:
const updateUser: ArkosPrismaInput<Prisma.UserUpdateInput> = {
// Single relation — no id needed for delete/disconnect
profile: { apiAction: "disconnect" },
// Array relation — id required to target the specific item
posts: [
{ id: 1, apiAction: "connect" }, // Connect existing
{ id: 2, title: "Updated", apiAction: "update" }, // Update
{ id: 3, apiAction: "delete" }, // Delete
{ id: 4, apiAction: "disconnect" } // Disconnect without deleting
]
};Valid apiAction values: "create", "connect", "update", "delete", "disconnect"
Type Safety with Interceptors
Use ArkosPrismaInput with interceptors for type-safe request manipulation:
import { ArkosRequest, ArkosResponse, ArkosNextFunction } from "arkos";
import { Prisma } from "@prisma/client";
import { ArkosPrismaInput } from "arkos/prisma";
type CreatePostBody = ArkosPrismaInput<Prisma.PostCreateInput>;
export const beforeCreateOne = [
async (
req: ArkosRequest<any, any, CreatePostBody>,
res: ArkosResponse,
next: ArkosNextFunction
) => {
// Type-safe access to flattened relations
req.body.author = { id: req.user!.id };
// Add defaults to all tags
if (req.body.tags) {
req.body.tags = req.body.tags.map(tag => ({
...tag,
type: "user-generated"
}));
}
next();
}
];What Arkos Does Not Handle
If you manually structure a relation field using Prisma's native format (connect, create, etc.), Arkos respects your structure and does not transform it. This allows full control when needed:
// Arkos respects this — no transformation applied
const customPost: Prisma.PostCreateInput = {
title: "Custom Post",
tags: {
connect: [{ id: 1 }, { id: 2 }],
create: [{ name: "New Tag" }]
}
};However, Arkos does not recursively transform manually structured fields. If you mix formats:
{
"subCategory": {
"create": {
"name": "New Sub Category",
"category": { "id": 3 } // ← Arkos won't transform this inner field
}
}
}To work around this, either:
- Use the flattened format for all levels
- Write the inner relation in full Prisma format (
{ connect: { id: 3 } })
Related
- Prisma Model Routes — The endpoints that use this relation handling
- Custom Queries — Default Prisma options per operation
- Interceptors — Run logic before/after operations
- ArkosPrismaInput API Reference — Full type utility documentation