Agent skill
openapi-to-components
Convert Synnovator OpenAPI spec into fully integrated Next.js frontend components. Reads .synnovator/openapi.yaml and frontend/components/pages/*.tsx, then generates API client (lib/api-client.ts), TypeScript types (lib/types.ts), server-side data fetching functions (lib/api/*.ts), and updates existing page components to replace hardcoded mock data with real API calls using Next.js App Router Server Components. Use when: (1) Need to wire frontend components to backend API endpoints (2) Want to replace mock/hardcoded data in page components with real API fetching (3) Adding a new page component that needs API integration (4) Regenerating API types after OpenAPI spec changes
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/openapi-to-components
SKILL.md
Auto OpenAPI to Components
Generate API client code and update Synnovator frontend components to fetch real data via Next.js Server Components.
Prerequisites
- OpenAPI spec at
.synnovator/openapi.yaml - Frontend components at
frontend/components/pages/*.tsx - Mapping reference at
docs/frontend-api-mapping.mdor references/frontend-api-mapping.md
Workflow
Phase 1: Read Inputs
- Read
.synnovator/openapi.yamlto get all endpoints, schemas, and enums - Read the target component(s) in
frontend/components/pages/to identify mock data variables and hardcoded values - Read references/frontend-api-mapping.md for the line-by-line mapping of mock data to API endpoints
Phase 2: Generate API Infrastructure
Generate these files in order:
2.1 TypeScript Types — frontend/lib/types.ts
Extract all components/schemas from the OpenAPI spec and convert to TypeScript interfaces:
// Example: Post schema -> TypeScript interface
export interface Post {
id: string;
title: string;
type: PostType;
tags: string[];
status: PostStatus;
like_count: number;
comment_count: number;
average_rating: number | null;
content?: string;
created_by?: string;
created_at: string;
updated_at: string;
}
export type PostType = "profile" | "team" | "category" | "for_category" | "certificate" | "general";
export type PostStatus = "draft" | "pending_review" | "published" | "rejected";
Conversion rules:
- OpenAPI
string->string - OpenAPI
integer->number - OpenAPI
numberwithformat: float->number - OpenAPI
boolean->boolean - OpenAPI
stringwithformat: date-time->string(ISO 8601) - OpenAPI
stringwithformat: uri->string - OpenAPI
stringwithformat: email->string - OpenAPI
arraywithitems->T[] - OpenAPI
objectwithadditionalProperties->Record<string, T> - OpenAPI
enum-> TypeScript union type - OpenAPI
$ref-> reference to another interface - OpenAPI
default: "null"->| nullunion - Fields listed in
requiredare non-optional; others use? - Paginated list schemas ->
PaginatedList<T>generic
Generate these paginated wrapper:
export interface PaginatedList<T> {
items: T[];
total: number;
skip: number;
limit: number;
}
2.2 API Client — frontend/lib/api-client.ts
Create a typed fetch wrapper for server-side use:
const API_BASE = process.env.API_BASE_URL || "http://localhost:8000";
export class ApiError extends Error {
constructor(public status: number, public code: string, message: string) {
super(message);
}
}
export async function apiFetch<T>(
path: string,
options?: RequestInit & { params?: Record<string, string | number | boolean | undefined> }
): Promise<T> {
const { params, ...fetchOptions } = options || {};
const url = new URL(path, API_BASE);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) url.searchParams.set(key, String(value));
});
}
const res = await fetch(url.toString(), {
...fetchOptions,
headers: {
"Content-Type": "application/json",
...fetchOptions?.headers,
},
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new ApiError(
res.status,
body?.error?.code || "UNKNOWN",
body?.error?.message || res.statusText
);
}
if (res.status === 204) return undefined as T;
return res.json();
}
2.3 Resource-Specific API Functions — frontend/lib/api/*.ts
Create one file per OpenAPI tag. Each function maps to one operation in the spec.
File naming: frontend/lib/api/{tag}.ts (e.g., posts.ts, categories.ts, users.ts, groups.ts, resources.ts, rules.ts, interactions.ts, admin.ts)
Pattern for each function:
// frontend/lib/api/posts.ts
import { apiFetch } from "../api-client";
import type { Post, PostCreate, PostUpdate, PaginatedList } from "../types";
export async function listPosts(params?: {
skip?: number;
limit?: number;
type?: string;
status?: string;
tags?: string[];
}) {
return apiFetch<PaginatedList<Post>>("/posts", { params, next: { revalidate: 60 } });
}
export async function getPost(postId: string) {
return apiFetch<Post>(`/posts/${postId}`, { next: { revalidate: 60 } });
}
export async function createPost(data: PostCreate) {
return apiFetch<Post>("/posts", { method: "POST", body: JSON.stringify(data) });
}
Mapping rules from OpenAPI to function names:
operationId: list_posts->listPostsoperationId: get_post->getPostoperationId: create_post->createPostoperationId: update_post->updatePostoperationId: delete_post->deletePost- Nested operations use compound names:
list_post_comments->listPostComments
For GET requests, add next: { revalidate: 60 } for ISR caching.
Phase 3: Update Page Components
For each page component, apply these transformations:
3.1 Convert to Async Server Components
Change the component from client-side to async server component:
// BEFORE (client component with mock data)
"use client"
const cards = [{ title: "...", author: "..." }]
export function Home() {
return <div>...</div>
}
// AFTER (server component with real data)
import { listPosts } from "@/lib/api/posts";
import { listCategories } from "@/lib/api/categories";
export default async function Home() {
const [postsData, categoriesData] = await Promise.all([
listPosts({ status: "published", limit: 6 }),
listCategories({ limit: 10 }),
]);
return <div>...</div>
}
3.2 Replace Mock Data Variables
For each mock variable identified in the mapping doc:
- Delete the
constdeclaration (e.g.,const cards = [...]) - Add the corresponding API import and call at the top of the async function
- Update JSX to reference the API response fields
Field mapping from API response to component props:
Post.title-> card title textPost.created_by-> author ID (requires separategetUsercall or embed)Post.tags-> Badge componentsPost.like_count-> like counter displayPost.comment_count-> comment counter displayPost.content-> markdown content areaCategory.name-> tab/filter labelCategory.cover_image-> banner/card image srcUser.display_name-> author name textUser.avatar_url-> Avatar image srcUser.bio-> user description textResource.url-> image/file srcGroup.name-> team name textGroup.description-> team description text
3.3 Extract Interactive Parts to Client Components
Server Components cannot use onClick, useState, useEffect etc. Extract interactive UI into separate client components:
frontend/components/
├── pages/ (server components - data fetching)
│ └── home.tsx
├── interactive/ (client components - user interactions)
│ ├── like-button.tsx
│ ├── comment-form.tsx
│ └── search-bar.tsx
└── ui/ (existing shadcn components)
Interactive elements to extract:
- Like/unlike button ->
components/interactive/like-button.tsx - Comment form ->
components/interactive/comment-form.tsx - Search bar ->
components/interactive/search-bar.tsx - Tab state management -> keep
"use client"wrapper per tab section - Follow/unfollow button ->
components/interactive/follow-button.tsx
Client components use Server Actions for mutations:
// frontend/app/actions/posts.ts
"use server"
import { likePost, unlikePost } from "@/lib/api/interactions";
export async function toggleLike(postId: string, isLiked: boolean) {
if (isLiked) {
await unlikePost(postId);
} else {
await likePost(postId);
}
}
Phase 4: Validate
After generating all files:
- Check TypeScript compilation:
npx tsc --noEmit - Verify no broken imports
- Ensure all
"use client"directives are only on interactive components - Confirm mock data variables have been removed
Component-to-API Quick Reference
| Component | Primary API calls |
|---|---|
home.tsx |
listPosts, listCategories, getUser |
post-list.tsx |
listPosts (type=team), listPosts (type=general) |
post-detail.tsx |
getPost, getUser, listPostComments, listPostResources, listPostRelated |
proposal-list.tsx |
listPosts (type=for_category), listCategories |
proposal-detail.tsx |
getPost, getUser, listPostComments, listPostRatings, listPostRelated, listPostResources |
category-detail.tsx |
getCategory, listCategoryRules, listCategoryPosts, listCategoryGroups |
user-profile.tsx |
getUser, listPosts (filtered by user), listResources |
team.tsx |
getGroup, listGroupMembers, getUser (per member) |
assets.tsx |
listResources |
following-list.tsx |
listUsers (API gap: no follow relationship endpoint) |
API Gaps
These frontend features have no matching API endpoint. Skip integration for these areas and leave mock data with a // TODO: API gap comment:
- Follow/unfollow relationships
- Search
- Notifications
- Favorites/bookmarks
- Post version history
- Resource tags, availability, deadline fields
- Group avatar
- Posts filtered by group
- User statistics aggregation
- Category-to-category relationships
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?