backend-engineer

SKILL开发
开发 未标注权限 许可证不明确

Supabase integration specialist. Handles database schema, authentication, Row Level Security (RLS), real-time subscriptions, and storage. Connects existing UI to real backend. Only called AFTER UI exists with mock data. Triggers: connect database, connect Supabase, add auth, make login, backend integration, real data, authentication, database schema.

一句话选型结论

按资料字段整理

backend-engineer是开发类的 Agent Skill,安装方式:GitHub 仓库,权限级别未标注,许可证不明确,没有现成安装命令,需按说明手动放置 SKILL.md。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类开发 安装方式GitHub 仓库 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 75 仓库 toh-framework
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

GitHub 仓库
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    按安装说明或仓库 README,把 SKILL.md 放到客户端的 skills 目录(一般一个技能一个文件夹)。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

开发
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方wasintoh仓库 toh-framework · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式GitHub 仓库安装包 wasintoh/toh-framework
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 未标注
正文体量14.3 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
写代码与改 bug

按需求补代码、定位并修掉报错。

读仓库与生成改动

让模型读懂项目结构,再给出可落地的改动。

跑测试与排查故障

按现象排查问题,补测试用例并复跑。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 基础信息齐全,可以先按说明装一个试试。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有现成安装命令,要按安装说明或仓库 README 手动放置。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在开发场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
backend-engineer当前 未标注权限 GitHub 仓库 未标注 未标注 无
技能审查员 浏览器控制 ClawHub 市场 877.9 万 OpenClaw 有
Vercel React 最佳实践 浏览器控制 GitHub 仓库 849.1 万 Codex、Claude、Cursor 有
GitHub 代码协作 联网访问 ClawHub 市场 763.3 万 OpenClaw 有
网页设计指南 联网访问 GitHub 仓库 668.6 万 Codex、Claude、Cursor 有
本体论 只读 ClawHub 市场 663.7 万 OpenClaw 有

SKILL.md 全文

14.3 KB
模型实际读到的说明书原文,来自技能仓库

Backend Engineer

Connect beautiful UI to real data. Supabase-first approach.

<core_principle>

The Integration Promise

Working UI with mock data → Connect Supabase → Real data flows automatically

We DON'T redesign. We DON'T add features. We connect what exists.
</core_principle>

<default_to_action>
NEVER ask:
- "Which database should I use?" → Supabase (our standard)
- "What's the schema?" → Derive from existing TypeScript types
- "What type of auth do you need?" → Supabase Auth with social providers

ALWAYS do:
- Create Supabase client configuration
- Generate schema from existing types
- Setup RLS policies
- Replace mock API calls with Supabase queries
</default_to_action>

<supabase_setup>

Initial Setup

1. Install Dependencies

npm install @supabase/supabase-js

2. Environment Variables

# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...

3. Client Configuration

// src/lib/supabase.ts
import { createClient } from &#x27;@supabase/supabase-js&#x27;
import { Database } from &#x27;@/types/supabase&#x27;

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient&lt;Database&gt;(supabaseUrl, supabaseKey)

4. Type Generation (after creating tables)

npx supabase gen types typescript --project-id xxxxx &gt; src/types/supabase.ts

</supabase_setup>

<schema_patterns>

Database Schema Patterns

Derive from TypeScript Types

// Existing type from dev-engineer
interface Product {
  id: string
  name: string
  description: string
  price: number
  stock: number
  category: string
  isActive: boolean
  createdAt: Date
  updatedAt: Date
}

// Becomes SQL
-- SQL for Supabase
create table products (
  id uuid default gen_random_uuid() primary key,
  name text not null,
  description text,
  price decimal(10,2) not null default 0,
  stock integer not null default 0,
  category text not null,
  is_active boolean not null default true,
  created_at timestamp with time zone default now(),
  updated_at timestamp with time zone default now()
);

-- Auto-update updated_at
create or replace function update_updated_at()
returns trigger as $$
begin
  new.updated_at = now();
  return new;
end;
$$ language plpgsql;

create trigger products_updated_at
  before update on products
  for each row execute function update_updated_at();

Common Tables

-- Users (extends Supabase auth.users)
create table profiles (
  id uuid references auth.users(id) primary key,
  full_name text,
  avatar_url text,
  role text default &#x27;user&#x27;,
  created_at timestamp with time zone default now(),
  updated_at timestamp with time zone default now()
);

-- Auto-create profile on signup
create or replace function handle_new_user()
returns trigger as $$
begin
  insert into profiles (id, full_name, avatar_url)
  values (
    new.id,
    new.raw_user_meta_data-&gt;&gt;&#x27;full_name&#x27;,
    new.raw_user_meta_data-&gt;&gt;&#x27;avatar_url&#x27;
  );
  return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute function handle_new_user();

</schema_patterns>

<rls_patterns>

Row Level Security (RLS)

Always Enable RLS

-- Enable RLS on all tables
alter table products enable row level security;
alter table profiles enable row level security;

Common Policies

Public Read, Authenticated Write

-- Anyone can view products
create policy &quot;Products are viewable by everyone&quot;
  on products for select
  using (true);

-- Only authenticated users can insert
create policy &quot;Authenticated users can create products&quot;
  on products for insert
  to authenticated
  with check (true);

-- Only owners can update (if user_id column exists)
create policy &quot;Users can update own products&quot;
  on products for update
  to authenticated
  using (user_id = auth.uid());

User-Owned Data

-- Users can only see their own data
create policy &quot;Users can view own orders&quot;
  on orders for select
  to authenticated
  using (user_id = auth.uid());

create policy &quot;Users can create own orders&quot;
  on orders for insert
  to authenticated
  with check (user_id = auth.uid());

Role-Based Access

-- Admins can do everything
create policy &quot;Admins have full access&quot;
  on products for all
  to authenticated
  using (
    exists (
      select 1 from profiles
      where profiles.id = auth.uid()
      and profiles.role = &#x27;admin&#x27;
    )
  );

</rls_patterns>

<auth_patterns>

Authentication

Setup Auth Provider

// src/lib/auth.ts
import { supabase } from &#x27;./supabase&#x27;

export async function signInWithEmail(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  })
  if (error) throw error
  return data
}

export async function signUp(email: string, password: string, fullName: string) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      data: { full_name: fullName }
    }
  })
  if (error) throw error
  return data
}

export async function signInWithGoogle() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: &#x27;google&#x27;,
    options: {
      redirectTo: `${window.location.origin}/auth/callback`
    }
  })
  if (error) throw error
  return data
}

export async function signInWithLine() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: &#x27;line&#x27; as any, // LINE needs custom setup
    options: {
      redirectTo: `${window.location.origin}/auth/callback`
    }
  })
  if (error) throw error
  return data
}

export async function signOut() {
  const { error } = await supabase.auth.signOut()
  if (error) throw error
}

export async function getCurrentUser() {
  const { data: { user } } = await supabase.auth.getUser()
  return user
}

Auth Context

// src/providers/auth-provider.tsx
&#x27;use client&#x27;

import { createContext, useContext, useEffect, useState } from &#x27;react&#x27;
import { User, Session } from &#x27;@supabase/supabase-js&#x27;
import { supabase } from &#x27;@/lib/supabase&#x27;

interface AuthContextType {
  user: User | null
  session: Session | null
  isLoading: boolean
}

const AuthContext = createContext&lt;AuthContextType&gt;({
  user: null,
  session: null,
  isLoading: true,
})

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState&lt;User | null&gt;(null)
  const [session, setSession] = useState&lt;Session | null&gt;(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() =&gt; {
    // Get initial session
    supabase.auth.getSession().then(({ data: { session } }) =&gt; {
      setSession(session)
      setUser(session?.user ?? null)
      setIsLoading(false)
    })

    // Listen for changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) =&gt; {
        setSession(session)
        setUser(session?.user ?? null)
      }
    )

    return () =&gt; subscription.unsubscribe()
  }, [])

  return (
    &lt;AuthContext.Provider value={{ user, session, isLoading }}&gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  )
}

export const useAuth = () =&gt; useContext(AuthContext)

Protected Routes (Next.js Middleware)

// src/middleware.ts
import { createMiddlewareClient } from &#x27;@supabase/auth-helpers-nextjs&#x27;
import { NextResponse } from &#x27;next/server&#x27;
import type { NextRequest } from &#x27;next/server&#x27;

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  const { data: { session } } = await supabase.auth.getSession()

  // Protected routes
  if (!session &amp;&amp; req.nextUrl.pathname.startsWith(&#x27;/dashboard&#x27;)) {
    return NextResponse.redirect(new URL(&#x27;/login&#x27;, req.url))
  }

  // Redirect logged-in users from auth pages
  if (session &amp;&amp; (req.nextUrl.pathname === &#x27;/login&#x27; || req.nextUrl.pathname === &#x27;/register&#x27;)) {
    return NextResponse.redirect(new URL(&#x27;/dashboard&#x27;, req.url))
  }

  return res
}

export const config = {
  matcher: [&#x27;/dashboard/:path*&#x27;, &#x27;/login&#x27;, &#x27;/register&#x27;]
}

</auth_patterns>

<query_patterns>

Database Queries

CRUD Operations

// src/lib/api/products.ts
import { supabase } from &#x27;@/lib/supabase&#x27;
import { Product, CreateProductInput, PaginatedResponse } from &#x27;@/types&#x27;

export async function getProducts(
  page = 1, 
  pageSize = 10,
  search?: string
): Promise&lt;PaginatedResponse&lt;Product&gt;&gt; {
  let query = supabase
    .from(&#x27;products&#x27;)
    .select(&#x27;*&#x27;, { count: &#x27;exact&#x27; })

  if (search) {
    query = query.ilike(&#x27;name&#x27;, `%${search}%`)
  }

  const from = (page - 1) * pageSize
  const to = from + pageSize - 1

  const { data, error, count } = await query
    .range(from, to)
    .order(&#x27;created_at&#x27;, { ascending: false })

  if (error) throw error

  return {
    data: data ?? [],
    total: count ?? 0,
    page,
    pageSize,
    totalPages: Math.ceil((count ?? 0) / pageSize),
  }
}

export async function getProduct(id: string): Promise&lt;Product | null&gt; {
  const { data, error } = await supabase
    .from(&#x27;products&#x27;)
    .select(&#x27;*&#x27;)
    .eq(&#x27;id&#x27;, id)
    .single()

  if (error) throw error
  return data
}

export async function createProduct(input: CreateProductInput): Promise&lt;Product&gt; {
  const { data, error } = await supabase
    .from(&#x27;products&#x27;)
    .insert(input)
    .select()
    .single()

  if (error) throw error
  return data
}

export async function updateProduct(
  id: string, 
  input: Partial&lt;Product&gt;
): Promise&lt;Product&gt; {
  const { data, error } = await supabase
    .from(&#x27;products&#x27;)
    .update(input)
    .eq(&#x27;id&#x27;, id)
    .select()
    .single()

  if (error) throw error
  return data
}

export async function deleteProduct(id: string): Promise&lt;void&gt; {
  const { error } = await supabase
    .from(&#x27;products&#x27;)
    .delete()
    .eq(&#x27;id&#x27;, id)

  if (error) throw error
}

Real-time Subscriptions

// Subscribe to changes
export function subscribeToProducts(
  callback: (payload: any) =&gt; void
) {
  return supabase
    .channel(&#x27;products_changes&#x27;)
    .on(
      &#x27;postgres_changes&#x27;,
      { event: &#x27;*&#x27;, schema: &#x27;public&#x27;, table: &#x27;products&#x27; },
      callback
    )
    .subscribe()
}

// Usage in component
useEffect(() =&gt; {
  const channel = subscribeToProducts((payload) =&gt; {
    console.log(&#x27;Change received!&#x27;, payload)
    refetchProducts()
  })

  return () =&gt; {
    supabase.removeChannel(channel)
  }
}, [])

</query_patterns>

<storage_patterns>

File Storage

Upload Files

// src/lib/storage.ts
import { supabase } from &#x27;./supabase&#x27;

export async function uploadFile(
  bucket: string,
  path: string,
  file: File
): Promise&lt;string&gt; {
  const { data, error } = await supabase.storage
    .from(bucket)
    .upload(path, file, {
      cacheControl: &#x27;3600&#x27;,
      upsert: false
    })

  if (error) throw error

  // Get public URL
  const { data: { publicUrl } } = supabase.storage
    .from(bucket)
    .getPublicUrl(data.path)

  return publicUrl
}

export async function deleteFile(bucket: string, path: string): Promise&lt;void&gt; {
  const { error } = await supabase.storage
    .from(bucket)
    .remove([path])

  if (error) throw error
}

Image Upload Component

// src/components/image-upload.tsx
&#x27;use client&#x27;

import { useState } from &#x27;react&#x27;
import { uploadFile } from &#x27;@/lib/storage&#x27;
import { Button } from &#x27;@/components/ui/button&#x27;
import { Upload, X } from &#x27;lucide-react&#x27;

interface ImageUploadProps {
  onUpload: (url: string) =&gt; void
  bucket?: string
}

export function ImageUpload({ onUpload, bucket = &#x27;images&#x27; }: ImageUploadProps) {
  const [isUploading, setIsUploading] = useState(false)
  const [preview, setPreview] = useState&lt;string | null&gt;(null)

  const handleUpload = async (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const file = e.target.files?.[0]
    if (!file) return

    setIsUploading(true)
    setPreview(URL.createObjectURL(file))

    try {
      const path = `${Date.now()}-${file.name}`
      const url = await uploadFile(bucket, path, file)
      onUpload(url)
    } catch (error) {
      console.error(&#x27;Upload failed:&#x27;, error)
      setPreview(null)
    } finally {
      setIsUploading(false)
    }
  }

  return (
    &lt;div className=&quot;space-y-2&quot;&gt;
      {preview ? (
        &lt;div className=&quot;relative inline-block&quot;&gt;
          &lt;img src={preview} alt=&quot;Preview&quot; className=&quot;w-32 h-32 object-cover rounded&quot; /&gt;
          &lt;button
            onClick={() =&gt; setPreview(null)}
            className=&quot;absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1&quot;
          &gt;
            &lt;X className=&quot;h-4 w-4&quot; /&gt;
          &lt;/button&gt;
        &lt;/div&gt;
      ) : (
        &lt;label className=&quot;flex flex-col items-center justify-center w-32 h-32 border-2 border-dashed rounded cursor-pointer hover:border-primary&quot;&gt;
          &lt;Upload className=&quot;h-8 w-8 text-slate-400&quot; /&gt;
          &lt;span className=&quot;text-xs text-slate-500 mt-2&quot;&gt;Upload image&lt;/span&gt;
          &lt;input
            type=&quot;file&quot;
            accept=&quot;image/*&quot;
            onChange={handleUpload}
            className=&quot;hidden&quot;
            disabled={isUploading}
          /&gt;
        &lt;/label&gt;
      )}
    &lt;/div&gt;
  )
}

</storage_patterns>

<migration_checklist>

Migration Checklist (Mock → Supabase)

Setup Phase

  • [ ] Create Supabase project
  • [ ] Add environment variables
  • [ ] Install @supabase/supabase-js
  • [ ] Create supabase client

Schema Phase

  • [ ] Convert TypeScript types to SQL
  • [ ] Create tables in Supabase
  • [ ] Setup triggers (updated_at, etc.)
  • [ ] Generate TypeScript types from schema

Security Phase

  • [ ] Enable RLS on all tables
  • [ ] Create appropriate policies
  • [ ] Setup auth providers (if needed)

Migration Phase

  • [ ] Replace mock API functions with Supabase queries
  • [ ] Update Zustand stores to use new API
  • [ ] Test all CRUD operations
  • [ ] Add real-time subscriptions (optional)

Verification Phase

  • [ ] All pages load with real data
  • [ ] Create/Update/Delete works
  • [ ] Auth flows work (if applicable)
  • [ ] RLS policies work correctly
    </migration_checklist>

常见问题

5 问
答案由本站依据条目字段整理
backend-engineer怎么安装?

没有现成安装命令:按安装说明或仓库 README,把 SKILL.md 放进客户端的 skills 目录(一般一个技能一个文件夹)。

backend-engineer支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

backend-engineer权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

backend-engineer收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

backend-engineer用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。