Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions src/shared/data-access/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Data Access Layer

Simple pattern for organizing database queries. Keep your Elysia endpoints clean by putting all database logic in dedicated files.

## Structure

```
src/shared/data-access/
├── posts.ts # Post-related queries
├── comments.ts # Comment-related queries
└── index.ts # Export all functions
```
Comment on lines +7 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language specification to the fenced code block.

The directory structure block should specify a language for the fenced code block to comply with markdown standards.

🔎 Proposed fix
-```
+```text
 src/shared/data-access/
 ├── posts.ts          # Post-related queries
 ├── comments.ts       # Comment-related queries
 └── index.ts          # Export all functions
-```
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
src/shared/data-access/
├── posts.ts # Post-related queries
├── comments.ts # Comment-related queries
└── index.ts # Export all functions
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In src/shared/data-access/README.md around lines 7 to 12, the fenced code block
showing the directory structure is missing a language specifier; update the
opening fence to include a language (e.g., change ``` to ```text) so the block
becomes a valid fenced code block with a language tag and keep the existing
content and closing fence unchanged.


## Pattern

Each file contains simple functions that interact with the database:

```typescript
// src/shared/data-access/posts.ts
import "server-only";
import { eq } from "drizzle-orm";
import { db } from "../db/client";
import { post } from "../db/schema";

export async function getPostById(id: string) {
const [result] = await db
.select()
.from(post)
.where(eq(post.id, id))
.limit(1);
return result ?? null;
}

export async function getAllPosts() {
return db.select().from(post);
}

export async function createPost(data: { title: string; content: string }) {
const [newPost] = await db
.insert(post)
.values({
id: crypto.randomUUID(),
...data,
})
.returning();
return newPost;
}
```

## Usage in Elysia Endpoints

```typescript
// app/api/[[...slugs]]/route.ts
import { Elysia } from "elysia";
import { getPostById, getAllPosts, createPost } from "@/shared/data-access";

export const app = new Elysia({ prefix: "/api" })
.get("/posts", async () => {
const posts = await getAllPosts();
return posts;
})
.get("/posts/:id", async ({ params }) => {
const post = await getPostById(params.id);
if (!post) {
throw new Error("Post not found");
}
return post;
})
.post("/posts", async ({ body }) => {
const post = await createPost(body);
return post;
});
```

## Why This Pattern?

1. **Separation of concerns**: Database logic separate from API logic
2. **Reusable**: Use the same functions in Server Components, API routes, server actions
3. **Easy to test**: Mock these functions in tests
4. **Easy to migrate**: If you switch ORMs, just update these files
5. **Type-safe**: Full TypeScript support

## Example: Adding a New Entity

1. **Create the schema**:

```typescript
// src/shared/db/schema/posts.ts
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";

export const post = pgTable("post", {
id: text("id").primaryKey(),
title: text("title").notNull(),
content: text("content").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => new Date())
.notNull(),
});
```

2. **Create data access functions**:

```typescript
// src/shared/data-access/posts.ts
import "server-only";
import { db } from "../db/client";
import { post } from "../db/schema";
import { eq } from "drizzle-orm";

export async function getPostById(id: string) {
const [result] = await db.select().from(post).where(eq(post.id, id)).limit(1);
return result ?? null;
}

export async function getAllPosts() {
return db.select().from(post);
}

export async function createPost(data: { title: string; content: string }) {
const [newPost] = await db
.insert(post)
.values({
id: crypto.randomUUID(),
...data,
})
.returning();
return newPost;
}

export async function updatePost(
id: string,
data: { title?: string; content?: string },
) {
const [updated] = await db
.update(post)
.set(data)
.where(eq(post.id, id))
.returning();
return updated ?? null;
}

export async function deletePost(id: string) {
await db.delete(post).where(eq(post.id, id));
}
```

3. **Export from index**:

```typescript
// src/shared/data-access/index.ts
export * from "./posts";
```

4. **Use in your API**:

```typescript
import { getAllPosts, createPost } from "@/shared/data-access";

const app = new Elysia()
.get("/posts", () => getAllPosts())
.post("/posts", ({ body }) => createPost(body));
```

That's it! Simple, clean, and easy to understand.
13 changes: 13 additions & 0 deletions src/shared/data-access/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Data Access Layer
*
* Export all data access functions from here.
* When you create a new data access file (e.g., posts.ts, comments.ts),
* add the export here:
*
* export * from "./posts";
* export * from "./comments";
*/

// Example:
// export * from "./posts";