CRUD Implementation Skill
Implement comprehensive CRUD operations for Supabase tables with best practices.
Purpose
Generate production-ready CRUD operations including queries, mutations, error handling, type safety, and RLS-aware implementations.
When to Use
- User requests CRUD functionality
- Needs API endpoints for data operations
- Asks for database interaction code
- Wants to implement data management features
- Requests type-safe database operations
Instructions
Analyze Requirements
- Identify target table and columns
- Understand relationships and foreign keys
- Check RLS policies on table
- Determine validation rules
- Identify required permissions
Implement Read Operations
- Single record fetch by ID
- List with filtering and pagination
- Search functionality
- Include related data with joins
- Handle not found cases
Implement Create Operations
- Input validation
- Required field checks
- Foreign key validation
- Return created record with ID
- Handle duplicate key errors
Implement Update Operations
- Partial update support
- Optimistic locking if needed
- Validate ownership via RLS
- Return updated record
- Handle not found cases
Implement Delete Operations
- Soft delete vs hard delete
- Cascade handling
- Validate ownership
- Return success confirmation
- Handle foreign key violations
Add Error Handling
- Wrap operations in try-catch
- Provide meaningful error messages
- Handle common Postgres errors
- Return structured error responses
- Log errors appropriately
Examples
TypeScript Implementation
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
const supabase = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
)
// READ: Get single post by ID
export async function getPost(id: string) {
const { data, error } = await supabase
.from('posts')
.select(`
*,
author:profiles!author_id(*),
comments(count)
`)
.eq('id', id)
.single()
if (error) {
throw new Error(`Failed to fetch post: ${error.message}`)
}
return data
}
// READ: List posts with pagination
export async function listPosts(options: {
page?: number
perPage?: number
published?: boolean
}) {
const { page = 1, perPage = 20, published } = options
const start = (page - 1) * perPage
const end = start + perPage - 1
let query = supabase
.from('posts')
.select('*, author:profiles!author_id(username)', { count: 'exact' })
if (published !== undefined) {
query = query.eq('published', published)
}
const { data, error, count } = await query
.order('created_at', { ascending: false })
.range(start, end)
if (error) {
throw new Error(`Failed to list posts: ${error.message}`)
}
return {
data,
pagination: {
page,
perPage,
total: count,
totalPages: Math.ceil((count || 0) / perPage)
}
}
}
// CREATE: Insert new post
export async function createPost(input: {
title: string
content: string
authorId: string
published?: boolean
}) {
const { data, error } = await supabase
.from('posts')
.insert({
title: input.title,
content: input.content,
author_id: input.authorId,
published: input.published ?? false,
slug: generateSlug(input.title)
})
.select()
.single()
if (error) {
if (error.code === '23505') {
throw new Error('A post with this title already exists')
}
throw new Error(`Failed to create post: ${error.message}`)
}
return data
}
// UPDATE: Update existing post
export async function updatePost(
id: string,
updates: {
title?: string
content?: string
published?: boolean
}
) {
const { data, error } = await supabase
.from('posts')
.update(updates)
.eq('id', id)
.select()
.single()
if (error) {
if (error.code === 'PGRST116') {
throw new Error('Post not found or access denied')
}
throw new Error(`Failed to update post: ${error.message}`)
}
return data
}
// DELETE: Remove post
export async function deletePost(id: string) {
const { error } = await supabase
.from('posts')
.delete()
.eq('id', id)
if (error) {
if (error.code === 'PGRST116') {
throw new Error('Post not found or access denied')
}
throw new Error(`Failed to delete post: ${error.message}`)
}
return { success: true }
}
function generateSlug(title: string): string {
return title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
Output Format
Provide:
- Complete CRUD function implementations
- Proper TypeScript types
- Error handling for all cases
- Usage examples
- Testing recommendations
1---2name: implement-crud3description: Implement complete CRUD (Create, Read, Update, Delete) operations for Supabase tables with proper error handling, validation, and RLS. Triggers when user requests data operations, API endpoints, or database interactions.4---5
6# CRUD Implementation Skill
7
8Implement comprehensive CRUD operations for Supabase tables with best practices.
9
10## Purpose
11
12Generate production-ready CRUD operations including queries, mutations, error handling, type safety, and RLS-aware implementations.
13
14## When to Use
15
16- User requests CRUD functionality
17- Needs API endpoints for data operations
18- Asks for database interaction code
19- Wants to implement data management features
20- Requests type-safe database operations
21
22## Instructions
23
241. **Analyze Requirements**
25 - Identify target table and columns
26 - Understand relationships and foreign keys
27 - Check RLS policies on table
28 - Determine validation rules
29 - Identify required permissions
30
312. **Implement Read Operations**
32 - Single record fetch by ID
33 - List with filtering and pagination
34 - Search functionality
35 - Include related data with joins
36 - Handle not found cases
37
383. **Implement Create Operations**
39 - Input validation
40 - Required field checks
41 - Foreign key validation
42 - Return created record with ID
43 - Handle duplicate key errors
44
454. **Implement Update Operations**
46 - Partial update support
47 - Optimistic locking if needed
48 - Validate ownership via RLS
49 - Return updated record
50 - Handle not found cases
51
525. **Implement Delete Operations**
53 - Soft delete vs hard delete
54 - Cascade handling
55 - Validate ownership
56 - Return success confirmation
57 - Handle foreign key violations
58
596. **Add Error Handling**
60 - Wrap operations in try-catch
61 - Provide meaningful error messages
62 - Handle common Postgres errors
63 - Return structured error responses
64 - Log errors appropriately
65
66## Examples
67
68### TypeScript Implementation
69
70```typescript
71import { createClient } from '@supabase/supabase-js'
72import type { Database } from './database.types'
73
74const supabase = createClient<Database>(
75 process.env.SUPABASE_URL!,
76 process.env.SUPABASE_ANON_KEY!
77)
78
79// READ: Get single post by ID
80export async function getPost(id: string) {
81 const { data, error } = await supabase
82 .from('posts')
83 .select(`
84 *,
85 author:profiles!author_id(*),
86 comments(count)
87 `)
88 .eq('id', id)
89 .single()
90
91 if (error) {
92 throw new Error(`Failed to fetch post: ${error.message}`)
93 }
94
95 return data
96}
97
98// READ: List posts with pagination
99export async function listPosts(options: {
100 page?: number
101 perPage?: number
102 published?: boolean
103}) {
104 const { page = 1, perPage = 20, published } = options
105 const start = (page - 1) * perPage
106 const end = start + perPage - 1
107
108 let query = supabase
109 .from('posts')
110 .select('*, author:profiles!author_id(username)', { count: 'exact' })
111
112 if (published !== undefined) {
113 query = query.eq('published', published)
114 }
115
116 const { data, error, count } = await query
117 .order('created_at', { ascending: false })
118 .range(start, end)
119
120 if (error) {
121 throw new Error(`Failed to list posts: ${error.message}`)
122 }
123
124 return {
125 data,
126 pagination: {
127 page,
128 perPage,
129 total: count,
130 totalPages: Math.ceil((count || 0) / perPage)
131 }
132 }
133}
134
135// CREATE: Insert new post
136export async function createPost(input: {
137 title: string
138 content: string
139 authorId: string
140 published?: boolean
141}) {
142 const { data, error } = await supabase
143 .from('posts')
144 .insert({
145 title: input.title,
146 content: input.content,
147 author_id: input.authorId,
148 published: input.published ?? false,
149 slug: generateSlug(input.title)
150 })
151 .select()
152 .single()
153
154 if (error) {
155 if (error.code === '23505') {
156 throw new Error('A post with this title already exists')
157 }
158 throw new Error(`Failed to create post: ${error.message}`)
159 }
160
161 return data
162}
163
164// UPDATE: Update existing post
165export async function updatePost(
166 id: string,
167 updates: {
168 title?: string
169 content?: string
170 published?: boolean
171 }
172) {
173 const { data, error } = await supabase
174 .from('posts')
175 .update(updates)
176 .eq('id', id)
177 .select()
178 .single()
179
180 if (error) {
181 if (error.code === 'PGRST116') {
182 throw new Error('Post not found or access denied')
183 }
184 throw new Error(`Failed to update post: ${error.message}`)
185 }
186
187 return data
188}
189
190// DELETE: Remove post
191export async function deletePost(id: string) {
192 const { error } = await supabase
193 .from('posts')
194 .delete()
195 .eq('id', id)
196
197 if (error) {
198 if (error.code === 'PGRST116') {
199 throw new Error('Post not found or access denied')
200 }
201 throw new Error(`Failed to delete post: ${error.message}`)
202 }
203
204 return { success: true }
205}
206
207function generateSlug(title: string): string {
208 return title
209 .toLowerCase()
210 .replace(/[^a-z0-9]+/g, '-')
211 .replace(/^-|-$/g, '')
212}
213```
214
215## Output Format
216
217Provide:
2181. Complete CRUD function implementations
2192. Proper TypeScript types
2203. Error handling for all cases
2214. Usage examples
2225. Testing recommendations