Guides
Building a Blog
Create a posts collection, fetch content in your frontend, and let clients edit through the Admin UI.
1. Define the collection
// dyrected.config.ts
import { defineConfig } from '@dyrected/core'
import { SqliteAdapter } from '@dyrected/db-sqlite'
export default defineConfig({
db: new SqliteAdapter({ filename: './dyrected.db' }),
collections: [
{
slug: 'posts',
access: { read: () => true },
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'slug', type: 'text', required: true },
{ name: 'content', type: 'richtext' },
{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
{ name: 'publishedAt', type: 'date' },
],
},
],
})2. Mount the API
// app/dyrected/[...route]/route.ts
import { createDyrectedApp } from '@dyrected/core/server'
import config from '@/dyrected.config'
const app = createDyrectedApp(config)
export const GET = app.fetch
export const POST = app.fetch
export const PATCH = app.fetch
export const DELETE = app.fetch// nuxt.config.ts
import config from './dyrected.config'
export default defineNuxtConfig({
modules: ['@dyrected/nuxt'],
dyrected: { ...config, apiBase: '/dyrected' },
})3. Fetch the post list
// app/blog/page.tsx
import { createClient } from '@dyrected/sdk'
const client = createClient({
baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL!,
apiKey: process.env.DYRECTED_API_KEY!,
})
export default async function BlogPage() {
const { docs: posts } = await client.collection('posts').find({
where: { status: { equals: 'published' } },
sort: '-publishedAt',
})
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
)
}<!-- pages/blog/index.vue -->
<script setup lang="ts">
const { data } = await useDyrectedFind('posts', {
where: { status: { equals: 'published' } },
sort: '-publishedAt',
})
</script>
<template>
<ul>
<li v-for="post in data?.docs" :key="post.id">
<NuxtLink :to="`/blog/${post.slug}`">{{ post.title }}</NuxtLink>
</li>
</ul>
</template>// app/blog/page.tsx
export default async function BlogPage() {
const res = await fetch(
`${process.env.NEXT_PUBLIC_DYRECTED_URL}/collections/posts?where[status][equals]=published&sort=-publishedAt`,
{ headers: { 'x-api-key': process.env.DYRECTED_API_KEY! } }
)
const { docs } = await res.json()
return (
<ul>
{docs.map((post: any) => (
<li key={post.id}><a href={`/blog/${post.slug}`}>{post.title}</a></li>
))}
</ul>
)
}4. Fetch a single post
// app/blog/[slug]/page.tsx
import { createClient } from '@dyrected/sdk'
const client = createClient({
baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL!,
apiKey: process.env.DYRECTED_API_KEY!,
})
export default async function PostPage({ params }: { params: { slug: string } }) {
const { docs } = await client.collection('posts').find({
where: { slug: { equals: params.slug } },
limit: 1,
})
const post = docs[0]
if (!post) return <div>Not found</div>
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useDyrectedDoc('posts', route.params.slug as string)
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<div v-html="post.content" />
</article>
<div v-else>Not found</div>
</template>// app/blog/[slug]/page.tsx
export default async function PostPage({ params }: { params: { slug: string } }) {
const res = await fetch(
`${process.env.NEXT_PUBLIC_DYRECTED_URL}/collections/posts?where[slug][equals]=${params.slug}&limit=1`,
{ headers: { 'x-api-key': process.env.DYRECTED_API_KEY! } }
)
const { docs } = await res.json()
const post = docs[0]
if (!post) return <div>Not found</div>
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}5. Revalidate on publish
// dyrected.config.ts
import { revalidatePath } from 'next/cache'
hooks: {
afterChange: [
async ({ doc }) => {
if (doc.status === 'published') {
revalidatePath('/blog')
revalidatePath(`/blog/${doc.slug}`)
}
},
],
}// dyrected.config.ts
hooks: {
afterChange: [
async ({ doc }) => {
if (doc.status === 'published') {
await $fetch('/api/revalidate', {
method: 'POST',
body: { slug: doc.slug },
})
}
},
],
}6. Embed the Admin UI
'use client'
import { AdminUI } from '@dyrected/admin'
import { useRouter } from 'next/navigation'
import '@dyrected/admin/dist/index.css'
export default function AdminPage() {
const router = useRouter()
return (
<AdminUI
baseUrl={process.env.NEXT_PUBLIC_DYRECTED_URL!}
apiKey={process.env.NEXT_PUBLIC_DYRECTED_API_KEY!}
basename="/admin"
onNavigate={(path) => router.push(`/admin${path}`)}
/>
)
}The Admin UI mounts automatically at /admin when you add the @dyrected/nuxt module. Configure the path in nuxt.config.ts:
dyrected: {
...config,
adminPath: '/admin',
}See Admin UI Overview for setup details.