Astro calendar_today 23 апр. 2026 г. schedule 2 мин

Astro + PocketBase: Полноценный бэкенд для вашего сайта

Как подключить PocketBase к Astro.js: авторизация пользователей, CRUD через API, файловое хранилище, realtime-подписки и деплой связки Astro + PocketBase.

person
Журналист
Автор
Astro + PocketBase — бэкенд и база данных

PocketBase — это self-hosted Backend-as-a-Service в одном бинарном файле. База данных SQLite, авторизация, файловое хранилище, realtime-подписки и REST API — всё в одном исполняемом файле размером ~25 MB. В паре с Astro это один из лучших стеков для быстрой разработки.

Почему PocketBase + Astro?

  • Простота деплоя: один бинарник на VPS, никаких сложных настроек
  • Self-hosted: ваши данные на вашем сервере, никаких Firebase/Supabase vendor lock-in
  • Встроенная авторизация: email/пароль, OAuth (Google, GitHub, VK), magic link
  • Файловое хранилище: загрузка файлов прямо в PocketBase
  • Realtime: WebSocket-подписки на изменения коллекций
  • Бесплатно: открытый исходный код, MIT лицензия

Установка PocketBase

code
# Скачать последнюю версию для Linux
wget https://github.com/pocketbase/pocketbase/releases/latest/download/pocketbase_linux_amd64.zip
unzip pocketbase_linux_amd64.zip

# Запустить
./pocketbase serve
# → Admin UI: http://127.0.0.1:8090/_/
# → API:      http://127.0.0.1:8090/api/

На первом запуске создайте суперадмина через браузер. Затем создайте нужные коллекции.

Подключение к Astro

code
npm install pocketbase
code
// src/lib/pocketbase.ts
import PocketBase from 'pocketbase';

// Сервер: для SSR и API-роутов
export function createServerPB() {
  return new PocketBase(import.meta.env.PB_URL);
}

// Клиент: для Preact-островов (браузер)
export const pb = new PocketBase(import.meta.env.PUBLIC_PB_URL);
code
// src/env.d.ts
interface ImportMetaEnv {
  readonly PB_URL: string; // Серверный URL (внутренний)
  readonly PUBLIC_PB_URL: string; // Клиентский URL (публичный)
  readonly PB_ADMIN_EMAIL: string; // Для серверных операций
  readonly PB_ADMIN_PASSWORD: string;
}

CRUD-операции в SSR-страницах

code
---
// src/pages/posts.astro
export const prerender = false;

import { createServerPB } from '../lib/pocketbase';
const pb = createServerPB();

// Авторизоваться как суперадмин для серверных операций
await pb
  .collection('_superusers')
  .authWithPassword(import.meta.env.PB_ADMIN_EMAIL, import.meta.env.PB_ADMIN_PASSWORD);

// Получить список постов
const posts = await pb.collection('posts').getList(1, 20, {
  sort: '-created',
  filter: 'published = true',
  expand: 'author', // Развернуть связанную запись автора
});
---

<ul>
  {
    posts.items.map((post) => (
      <li>
        <h2>{post.title}</h2>
        <p>Автор: {post.expand?.author?.name}</p>
        <time>{new Date(post.created).toLocaleDateString('ru-RU')}</time>
      </li>
    ))
  }
</ul>

API-роуты с PocketBase

code
// src/pages/api/posts/index.ts
import type { APIRoute } from 'astro';
import { createServerPB } from '../../../lib/pocketbase';

// GET /api/posts — список постов
export const GET: APIRoute = async ({ url }) => {
  const pb = createServerPB();
  const page = Number(url.searchParams.get('page') ?? 1);
  const perPage = Number(url.searchParams.get('perPage') ?? 20);

  const posts = await pb.collection('posts').getList(page, perPage, {
    sort: '-created',
    filter: 'published = true',
  });

  return new Response(JSON.stringify(posts), {
    headers: { 'Content-Type': 'application/json' },
  });
};

// POST /api/posts — создать пост (требует авторизации)
export const POST: APIRoute = async ({ request, locals }) => {
  if (!locals.user) {
    return new Response('Unauthorized', { status: 401 });
  }

  const pb = createServerPB();
  pb.authStore.loadFromCookie(request.headers.get('cookie') ?? '');

  const data = await request.json();
  const post = await pb.collection('posts').create({
    ...data,
    author: locals.user.id,
    published: false, // Черновик
  });

  return new Response(JSON.stringify(post), { status: 201 });
};

Авторизация пользователей

code
---
// src/pages/login.astro
export const prerender = false;

import { createServerPB } from '../lib/pocketbase';

let error = '';

if (Astro.request.method === 'POST') {
  const data = await Astro.request.formData();
  const pb = createServerPB();

  try {
    await pb
      .collection('users')
      .authWithPassword(data.get('email') as string, data.get('password') as string);

    // Экспортировать сессию в куки
    Astro.response.headers.append(
      'Set-Cookie',
      pb.authStore.exportToCookie({ httpOnly: true, sameSite: 'strict' }),
    );

    return Astro.redirect('/dashboard');
  } catch {
    error = 'Неверный email или пароль';
  }
}
---

{error && <p class="error">{error}</p>}
<form method="POST">
  <input name="email" type="email" placeholder="Email" required />
  <input name="password" type="password" placeholder="Пароль" required />
  <button type="submit">Войти</button>
</form>

OAuth-авторизация (Google, GitHub, VK)

code
---
// src/pages/auth/oauth.astro
export const prerender = false;

import { createServerPB } from '../../lib/pocketbase';

const code = Astro.url.searchParams.get('code');
const state = Astro.url.searchParams.get('state');
const provider = Astro.url.searchParams.get('provider') ?? 'google';

if (code && state) {
  const pb = createServerPB();

  try {
    await pb
      .collection('users')
      .authWithOAuth2Code(
        provider,
        code,
        '',
        `${import.meta.env.PUBLIC_SITE_URL}/auth/oauth?provider=${provider}`,
      );

    Astro.response.headers.append('Set-Cookie', pb.authStore.exportToCookie());
    return Astro.redirect('/dashboard');
  } catch {
    return Astro.redirect('/login?error=oauth');
  }
}
---

Файловое хранилище

PocketBase хранит файлы прямо в файловой системе сервера:

code
// src/components/FileUpload.tsx (Preact-остров)
import { pb } from '../lib/pocketbase';

export default function FileUpload({ recordId }: { recordId: string }) {
  async function handleUpload(e: Event) {
    const input = e.target as HTMLInputElement;
    const file = input.files?.[0];
    if (!file) return;

    const formData = new FormData();
    formData.append('avatar', file);

    // Обновить запись пользователя с файлом
    const updated = await pb.collection('users').update(recordId, formData);

    // Получить URL файла
    const avatarUrl = pb.files.getUrl(updated, updated.avatar, { thumb: '100x100' });
    console.log('Avatar:', avatarUrl);
  }

  return (
    <label>
      Загрузить фото
      <input type="file" accept="image/*" onChange={handleUpload} />
    </label>
  );
}

Realtime-подписки

code
// src/components/LiveFeed.tsx (Preact-остров)
import { useEffect, useState } from 'preact/hooks';
import { pb } from '../lib/pocketbase';

interface Message {
  id: string;
  text: string;
  created: string;
}

export default function LiveFeed() {
  const [messages, setMessages] = useState<Message[]>([]);

  useEffect(() => {
    // Подписаться на изменения коллекции
    pb.collection('messages').subscribe('*', (e) => {
      if (e.action === 'create') {
        setMessages((prev) => [e.record as Message, ...prev]);
      }
    });

    // Загрузить начальные данные
    pb.collection('messages')
      .getList(1, 20, { sort: '-created' })
      .then((r) => setMessages(r.items as Message[]));

    return () => {
      pb.collection('messages').unsubscribe('*');
    };
  }, []);

  return (
    <ul>
      {messages.map((m) => (
        <li key={m.id}>
          <span>{m.text}</span>
          <time>{new Date(m.created).toLocaleTimeString('ru-RU')}</time>
        </li>
      ))}
    </ul>
  );
}

Деплой: PocketBase на VPS + Astro на Cloudflare

PocketBase (на Beget VPS или любом Linux-сервере):

code
# Запуск как systemd-сервис
sudo nano /etc/systemd/system/pocketbase.service

[Unit]
Description=PocketBase
After=network.target

[Service]
User=www-data
WorkingDirectory=/opt/pocketbase
ExecStart=/opt/pocketbase/pocketbase serve --http 0.0.0.0:8090
Restart=always

[Install]
WantedBy=multi-user.target

sudo systemctl enable --now pocketbase

Astro — деплой на Cloudflare Pages (бесплатно). В astro.config.mjs укажите публичный URL PocketBase в переменных окружения.

Итог

PocketBase + Astro — идеальный стек для инди-разработчиков и небольших команд. Один бинарник заменяет целый стек (PostgreSQL + Auth + S3 + WebSockets). Astro обеспечивает быстрый фронтенд. Связка разворачивается за 30 минут и стоит ~$5/мес за VPS. Подробнее о выборе базы данных — в статье Базы данных для Astro.

Портрет автора Дмитрий Соколов

Senior Frontend Engineer / Tech Writer

Senior Frontend Engineer с 9-летним опытом. Специализируется на Astro.js и JAMstack.

Комментарии

Загрузка комментариев...

Оставить комментарий

Комментарии проходят модерацию перед публикацией. Правила

Рекомендуем к прочтению