Create API routes in server/api/:
// server/api/hello.ts
export default defineEventHandler((event) => {
return {
message: 'Hello from API!'
}
})
Access at /api/hello.
// server/api/posts/[id].ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return {
id,
title: 'Post Title'
}
})
// server/api/posts/create.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Process body
return {
success: true,
data: body
}
})
Create middleware in middleware/:
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const user = useUser()
const config = useRuntimeConfig()
if (!user.value) {
return navigateTo(config.public.loginUrl, { external: true })
}
})
Use in pages:
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
</script>
// middleware/analytics.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
// Track page view
console.log('Navigating to:', to.path)
})
// plugins/my-plugin.client.ts
export default defineNuxtPlugin((nuxtApp) => {
// Only runs on client
console.log('Client plugin loaded')
return {
provide: {
myFunction: () => 'Hello!'
}
}
})
// plugins/my-plugin.server.ts
export default defineNuxtPlugin((nuxtApp) => {
// Only runs on server
console.log('Server plugin loaded')
})
<script setup lang="ts">
const { $myFunction } = useNuxtApp()
console.log($myFunction()) // 'Hello!'
</script>
Share state across components:
// composables/useCounter.ts
export const useCounter = () => {
const count = useState('counter', () => 0)
const increment = () => count.value++
const decrement = () => count.value--
return {
count,
increment,
decrement
}
}
Use in components:
<script setup lang="ts">
const { count, increment } = useCounter()
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<UButton @click="increment">Increment</UButton>
</div>
</template>
Install Pinia for complex state:
npm install pinia @pinia/nuxt
Configure:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})
Create store:
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
email: ''
}),
actions: {
setUser(user: { name: string; email: string }) {
this.name = user.name
this.email = user.email
}
}
})
// composables/useFetch.ts
export const useApiFetch = <T>(url: string) => {
return useFetch<T>(url, {
baseURL: 'https://api.example.com',
headers: {
'Authorization': `Bearer ${useToken().value}`
}
})
}
// composables/useUser.ts
interface User {
id: number
name: string
email: string
}
export const useUser = () => {
const user = useState<User | null>('user', () => null)
const fetchUser = async () => {
const { data } = await useApiFetch<User>('/user')
user.value = data.value
}
return {
user,
fetchUser
}
}
<script setup lang="ts">
const HeavyComponent = defineAsyncComponent(() =>
import('~/components/HeavyComponent.vue')
)
</script>
<template>
<Suspense>
<HeavyComponent />
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<template>
<NuxtImg
src="/image.jpg"
width="800"
height="600"
format="webp"
quality="80"
loading="lazy"
/>
</template>
// nuxt.config.ts
export default defineNuxtConfig({
vite: {
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'vue-router']
}
}
}
}
}
})
Install Vitest:
npm install -D vitest @vue/test-utils
Create test:
// components/Button.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Button from './Button.vue'
describe('Button', () => {
it('renders properly', () => {
const wrapper = mount(Button, {
props: { label: 'Click me' }
})
expect(wrapper.text()).toContain('Click me')
})
})
Install Playwright:
npm install -D @playwright/test
Create test:
// tests/e2e/home.spec.ts
import { test, expect } from '@playwright/test'
test('homepage loads', async ({ page }) => {
await page.goto('/')
await expect(page.locator('h1')).toContainText('Welcome')
})
// server/middleware/csrf.ts
export default defineEventHandler((event) => {
if (event.method !== 'GET') {
const token = getCookie(event, 'csrf-token')
const headerToken = getHeader(event, 'x-csrf-token')
if (token !== headerToken) {
throw createError({
statusCode: 403,
message: 'Invalid CSRF token'
})
}
}
})
// server/middleware/rate-limit.ts
const requests = new Map()
export default defineEventHandler((event) => {
const ip = getRequestIP(event)
const now = Date.now()
const windowMs = 60000 // 1 minute
const max = 100 // max requests per window
const userRequests = requests.get(ip) || []
const recentRequests = userRequests.filter(
(time: number) => now - time < windowMs
)
if (recentRequests.length >= max) {
throw createError({
statusCode: 429,
message: 'Too many requests'
})
}
recentRequests.push(now)
requests.set(ip, recentRequests)
})