# Service-layer patterns

> How to keep services thin and explicit on top of better-drizzle.

URL: https://better-drizzle.com/docs/guides/service-patterns

better-drizzle works best when the service layer stays thin and explicit. The delegate API already removes the query glue, so services should add *domain meaning*, not re-wrap calls for their own sake.

## A good read

Compose exactly the shape the caller needs, once:



**better-drizzle**

```ts  
export function getUserProfile(userId: number) {
	return client.users.findUnique({
		where: { id: userId },
		include: {
			posts: {
				select: { id: true, title: true, published: true },
			},
		},
	});
}
```

**Drizzle**

```ts  
import { eq } from 'drizzle-orm';

export async function getUserProfile(userId: number) {
	const [user] = await db
		.select()
		.from(users)
		.where(eq(users.id, userId))
		.limit(1);
	if (!user) return null;

	// second round trip for the relation, then attach it by hand
	const userPosts = await db
		.select({ id: posts.id, title: posts.title, published: posts.published })
		.from(posts)
		.where(eq(posts.authorId, userId));

	return { ...user, posts: userPosts };
}
```

## A good write

Let `.throw()` carry the not-found decision so the happy path stays linear:



**better-drizzle**

```ts  
export function renameUser(userId: number, name: string) {
	return client.users
		.update({ where: { id: userId }, data: { name } })
		.throw(() => new Error('User not found'));
}
```

**Drizzle**

```ts  
import { eq } from 'drizzle-orm';

export async function renameUser(userId: number, name: string) {
	const [user] = await db
		.update(users)
		.set({ name })
		.where(eq(users.id, userId))
		.returning();
	if (!user) throw new Error('User not found');
	return user;
}
```

## A good transactional service

Keep multi-step writes inside a single [transaction](/docs/advanced/transactions):



**better-drizzle**

```ts  
export function createUserAndDraft(input: {
	email: string;
	name: string;
	title: string;
}) {
	return client.transaction(async (tx) => {
		const user = await tx.users.create({
			data: { email: input.email, name: input.name, active: true },
		});

		return tx.posts.create({
			data: { authorId: user.id, title: input.title, published: false },
		});
	});
}
```

**Drizzle**

```ts  
export function createUserAndDraft(input: {
	email: string;
	name: string;
	title: string;
}) {
	return db.transaction(async (tx) => {
		const [user] = await tx
			.insert(users)
			.values({ email: input.email, name: input.name, active: true })
			.returning();

		const [post] = await tx
			.insert(posts)
			.values({ authorId: user.id, title: input.title, published: false })
			.returning();
		return post;
	});
}
```

## What to avoid

* wrapping every delegate call in another helper without adding real value
* hiding query shape so far away that callers cannot see payload cost
* rebuilding pagination, relation loading, or not-found behavior by hand in every service
