Astro Content Collections: sposób na dane z MySQL
Podczas migracji strony do Astro pojawiło się wyzwanie: jak wykorzystać dane z istniejącej bazy MySQL w statycznie generowanych stronach? Z pomocą przychodzi funkcjonalność Content Collections. Pozwala ona w prosty sposób zintegrować dowolne źródło danych, zamieniając je w kolekcję, z której korzysta się tak samo, jak z plików lokalnych.
Architektura rozwiązania
Mechanizm opiera się na Loaderze. Jest to obiekt z metodą load, która wykonuje się w trakcie budowania projektu (build-time). Całość procesu sprowadza się do trzech kroków:
- Połączenie: Nawiązanie bezpiecznego połączenia z bazą danych.
- Synchronizacja: Pobranie danych i przekazanie ich do lokalnego magazynu (
store). - Walidacja: Użycie schematu Zod, aby upewnić się, że dane z MySQL są zgodne ze strukturą oczekiwaną przez aplikację.
Implementacja Loadera
Poniżej znajduje się kod odpowiedzialny za komunikację z bazą. Wykorzystano bibliotekę mysql2/promise do obsługi asynchronicznego pobierania rekordów.
import type { Loader } from 'astro/loaders';
import mysql from 'mysql2/promise';
export function mysqlLoader(config: { connection: any, table: string }): Loader {
return {
name: 'mysql-loader',
load: async ({ store, logger }) => {
const connection = await mysql.createConnection(config.connection);
const sql = `SELECT * FROM ${config.table} ORDER BY data DESC`;
logger.info("Executing: " + sql);
const [rows] = await connection.execute(sql);
store.clear();
(rows as any[]).forEach((row) => {
store.set({
id: String(row.idComments),
data: row,
});
});
await connection.end();
logger.info(`Loaded ${ (rows as any[]).length } rows from MySQL.`);
},
};
}
Integracja z kolekcją
Dzięki zdefiniowaniu schematu w defineCollection, TypeScript wymusza poprawną strukturę danych w całym projekcie:
const comments = defineCollection({
loader: mysqlLoader({
connection: {
host: import.meta.env.DB_HOST,
user: import.meta.env.DB_USER,
database: import.meta.env.DB_NAME,
password: import.meta.env.DB_PASSWORD
},
table: import.meta.env.DB_COMMENTS_TABLE,
}),
schema: z.object({
idComments: z.number(),
name: z.string(),
comment: z.string(),
email: z.string(),
web: z.string(),
newsId: z.number(),
data: z.string(),
}),
});
Dlaczego to podejście jest skuteczne?
- Wydajność: Dane są pobierane tylko podczas budowania strony. Użytkownik końcowy otrzymuje już wygenerowaną, statyczną treść.
- Typowanie: Dzięki zod błędy w strukturze danych bazy są wychwytywane na etapie budowania, a nie w trakcie przeglądania strony.
- Czystość kodu: Logika połączenia jest częścią projektu, nie wymaga dodatkowych konfiguracji i jest w pełni przejrzysta.