Nuxt is more than a Vue framework — it has a powerful server engine (Nitro) that turns it into a full-stack platform. API routes, middleware, caching, and auto-imports make backend development seamless.
Server API Routes
Create API endpoints by adding files to server/api/:
// server/api/users.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const users = await db.user.findMany({
take: Number(query.limit) || 10,
skip: Number(query.offset) || 0
})
return users
})
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const user = await db.user.create({ data: body })
return user
})
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id")
const user = await db.user.findUnique({ where: { id } })
if (!user) throw createError({ statusCode: 404, message: "User not found" })
return user
})
// server/api/users/[id].delete.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id")
await db.user.delete({ where: { id } })
return { deleted: true }
})
Server Middleware
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
const token = getHeader(event, "authorization")?.replace("Bearer ", "")
if (event.path.startsWith("/api/protected")) {
if (!token) throw createError({ statusCode: 401 })
event.context.user = await verifyToken(token)
}
})
// server/middleware/log.ts
export default defineEventHandler((event) => {
console.log(`${event.method} ${event.path}`)
})
Caching with Nitro
Nuxt's Nitro engine has built-in caching:
// server/api/stats.get.ts
export default defineCachedEventHandler(async () => {
const stats = await computeExpensiveStats()
return stats
}, {
maxAge: 60 * 60, // Cache for 1 hour
swr: true, // Stale-while-revalidate
name: "stats"
})
// Cached functions
const getSettings = defineCachedFunction(async (key: string) => {
return await db.setting.findUnique({ where: { key } })
}, {
maxAge: 300,
getKey: (key) => key
})
useFetch — Client-Side Data Fetching
Nuxt's composable for fetching from your API:
<script setup>
const { data: users, pending, error, refresh } = await useFetch("/api/users", {
query: { limit: 20 },
transform: (data) => data.map(u => ({ ...u, fullName: `${u.first} ${u.last}` }))
})
// Lazy fetch — doesn"t block navigation
const { data: stats } = useLazyFetch("/api/stats")
// With watch — refetch when params change
const page = ref(1)
const { data: posts } = await useFetch("/api/posts", {
query: { page },
watch: [page]
})
</script>
WebSocket Support
// server/routes/_ws.ts
export default defineWebSocketHandler({
open(peer) {
console.log("Connected:", peer.id)
peer.subscribe("chat")
},
message(peer, message) {
peer.publish("chat", message.text())
},
close(peer) {
console.log("Disconnected:", peer.id)
}
})
Server Utils & Auto-Imports
// server/utils/db.ts
import { PrismaClient } from "@prisma/client"
export const db = new PrismaClient()
// Auto-imported everywhere in server/ — no import needed!
// server/utils/auth.ts
export async function verifyToken(token: string) {
return jwt.verify(token, process.env.JWT_SECRET)
}
// Available in all server handlers automatically
Key Takeaways
- File-based API routes with HTTP method suffixes
- Nitro caching with SWR support
- Server middleware for auth, logging, rate limiting
- useFetch/useLazyFetch for type-safe client→server calls
- WebSocket support built-in
- Auto-imports for server utilities
Explore Nuxt docs for the full server guide.
Building web scrapers or data pipelines? Check out my Apify actors for ready-made solutions, or email spinov001@gmail.com for custom development.
Top comments (0)