Build type-safe Nuxt 3 applications with Nitro API patterns. Covers validation, fetch patterns, auth, SSR, composables, background tasks, and real-time features.
Provides patterns for building type-safe Nuxt 3 apps with Nitro APIs. Use when working with validation, auth, SSR, or real-time features in Nuxt projects.
/plugin marketplace add gallop-systems/claude-skills/plugin install nuxt-nitro-api@gallop-systems-claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
auth-patterns.mdcomposables-utils.mddeep-linking.mdexamples/auth-middleware.tsexamples/auth-utils.tsexamples/deep-link-page.vueexamples/service-util.tsexamples/sse-endpoint.tsexamples/validation-endpoint.tsfetch-patterns.mdnitro-tasks.mdpage-structure.mdserver-services.mdsse.mdssr-client.mdvalidation.mdThis skill provides patterns for building type-safe Nuxt 3 applications with Nitro backends.
Use this skill when:
For detailed patterns, see these topic-focused reference files:
Working examples from a Nuxt project:
$fetch<Type>() or useFetch<Type>()getValidatedQuery(), readValidatedBody() with Zod schemasimport.meta.client or onMounted/server directory)All h3 utilities auto-imported:
defineEventHandler, createError, getQuery, getValidatedQueryreadBody, readValidatedBody, getRouterParams, getValidatedRouterParamsgetCookie, setCookie, deleteCookie, getHeader, setHeaderFrom nuxt-auth-utils:
getUserSession, setUserSession, clearUserSession, requireUserSessionhashPassword, verifyPassworddefineOAuth*EventHandler (Google, GitHub, etc.)Need to import: z from "zod", fromZodError from "zod-validation-error"
All auto-imported:
ref, computed, watch, onMounted, etc.refDebounced, useLocalStorage, useUrlSearchParams, etc.useFetch, useAsyncData, useRoute, useRouter, useState, navigateTo/shared directory - Nuxt 3.14+)Code auto-imported on both client AND server. Use for:
// Pass Zod schema directly (h3 v2+)
const query = await getValidatedQuery(event, z.object({
search: z.string().optional(),
page: z.coerce.number().default(1),
}));
const body = await readValidatedBody(event, z.object({
email: z.string().email(),
name: z.string().min(1),
}));
// Template literals preserve type inference (fixed late 2024)
const userId = "123"; // Literal type "123"
const result = await $fetch(`/api/users/${userId}`);
// result is typed from the handler's return type
// NEVER do this - defeats type inference
const result = await $fetch<User>("/api/users/123"); // WRONG
// Basic - types inferred from Nitro
const { data, status, refresh } = await useFetch("/api/users");
// Reactive query params - auto-refetch on change
const search = ref("");
const debouncedSearch = refDebounced(search, 300); // Auto-imported
const { data } = await useFetch("/api/users", {
query: computed(() => ({
...(debouncedSearch.value ? { search: debouncedSearch.value } : {}),
})),
});
// Dynamic URL with getter
const userId = ref("123");
const { data } = await useFetch(() => `/api/users/${userId.value}`);
// New options (Nuxt 3.14+)
const { data } = await useFetch("/api/data", {
retry: 3, // Retry on failure
retryDelay: 1000, // Wait between retries
dedupe: "cancel", // Cancel previous request
delay: 300, // Debounce the request
});
// ONLY use $fetch in event handlers (onClick, onSubmit)
const handleSubmit = async () => {
const result = await $fetch("/api/users", {
method: "POST",
body: { name: "Test" },
});
};
// In server/utils/auth.ts
export async function getAuthenticatedUser(event: H3Event) {
const session = await getUserSession(event);
if (!session?.user) {
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
}
return session.user;
}
// In API handler
export default defineEventHandler(async (event) => {
const user = await getAuthenticatedUser(event);
// user is typed and guaranteed to exist
});
// Option 1: import.meta.client guard
watch(preference, (value) => {
if (import.meta.client) {
localStorage.setItem("pref", value);
}
});
// Option 2: onMounted
onMounted(() => {
const saved = localStorage.getItem("pref");
if (saved) preference.value = saved;
});
// Option 3: VueUse (SSR-safe)
const theme = useLocalStorage("theme", "light");
Needs Nuxt/Vue context (useRuntimeConfig, useRoute, refs)?
├─ YES → COMPOSABLE in /composables/use*.ts
└─ NO → UTIL in /utils/*.ts (client) or /server/utils/*.ts (server)
$fetch at top level - Causes double-fetch (SSR + client). Use useFetch.refDebounced to avoid excessive API calls.import.meta.client, onMounted, or <ClientOnly>.route and router explicitly.Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.