Primeros pasos
Inicio rápido
Instala ForgeCMS, define una colección y ejecuta operaciones CRUD mediante la Local API.
Este es el ForgeCMS más pequeño posible: la Local API con adaptadores en memoria, sin nada duradero. Para un proyecto real, sigue el Server quickstart verificado (libSQL, primer administrador, control de acceso) o el Angular/Analog quickstart (con el panel de administración incluido), ambos en inglés. Empieza desde tu propio repositorio:
pnpm add @forge-cms/core @forge-cms/runtime @forge-cms/db @forge-cms/auth @forge-cms/storage
Define una colección:
import { defineCollection, defineField } from '@forge-cms/core';
export const notes = defineCollection({
slug: 'notes',
fields: {
title: defineField.text({ required: true }),
data: defineField.json()
}
});
Crea el runtime:
import { InMemoryAuthAdapter } from '@forge-cms/auth';
import { InMemoryDatabaseAdapter } from '@forge-cms/db';
import { ForgeCmsRuntime } from '@forge-cms/runtime';
import { InMemoryStorageAdapter } from '@forge-cms/storage';
import { notes } from './cms.js';
export const runtime = new ForgeCmsRuntime({
collections: [notes],
adapters: {
database: new InMemoryDatabaseAdapter(),
auth: new InMemoryAuthAdapter(),
storage: new InMemoryStorageAdapter()
}
});
runtime.init();
await runtime.syncSchema();
Usa la Local API:
const created = await runtime.create({
collection: 'notes',
data: { title: 'Hello ForgeCMS', data: { source: 'docs' } }
});
const { docs } = await runtime.find({ collection: 'notes' });
const note = await runtime.findOne({ collection: 'notes', where: { title: 'Hello ForgeCMS' } });
await runtime.delete({ collection: 'notes', id: created.id });
runtime.syncSchema() crea las tablas que faltan y aplica los cambios aditivos seguros. Nunca
elimina, renombra, cambia el tipo ni rellena datos, y desde la versión 0.8.0 rechaza los cambios
que requerirían una migración de datos (esos los aplicas con una migración revisada); consulta
Schema upgrades (en inglés) para saber qué ocurre cuando cambias una
colección que ya tiene filas. Para integrarlo con el HTTP de tu framework, construye un ApiContext
y llama a los manejadores que exporta @forge-cms/runtime.
A continuación, monta el panel de administración y el cliente Angular: la Guía para un proyecto pequeño hace ambas cosas, con usuarios y sesiones con cookies.
Ejecutar la demo del repositorio
Requisitos previos
- Node >= 22 (
.nvmrc) - pnpm 10.11.0 — nunca npm ni yarn: el workspace depende de cómo enlaza pnpm
- git
1. Clonar y compilar
git clone https://github.com/Andersseen/ForgeCMS.git
cd ForgeCMS
pnpm install
pnpm build # required once: @forge-cms/* resolves to packages/*/dist
pnpm build no es opcional en un clon recién creado. El mapeo de rutas de TypeScript apunta
@forge-cms/* a packages/*/dist/index.d.ts, así que pnpm typecheck falla hasta que los paquetes
se han compilado una vez.
2. Ejecutar una aplicación
pnpm dev:www # landing page + /admin + the CRUD API
pnpm dev:demo # a full marketing site built on the CMS (apps/demo-aesthetics)
Abre la URL que aparece y ve a /admin. Las lecturas son públicas; crear, editar y borrar requiere
iniciar sesión:
| Aplicación | Correo electrónico | Contraseña | Rol |
|---|---|---|---|
apps/www (/admin/login) |
[email protected] |
forgecms-demo |
admin |
apps/demo-aesthetics (/login) |
[email protected] |
lumea-demo |
admin |
apps/demo-aesthetics (/login) |
[email protected] |
lumea-desk |
editor |
El desarrollo local usa adaptadores en memoria, así que los datos se reinician con cada recarga. Es lo esperado; las aplicaciones desplegadas persisten en Cloudflare D1.
3. Añadir una colección
Todo vive en un único archivo: apps/www/src/server/api/runtime.ts. Las rutas HTTP y el panel de
administración no dependen de ninguna colección concreta: leen lo que esté registrado ahí, así que no
hay un segundo lugar donde conectar nada.
const testimonials = defineCollection({
slug: 'testimonials',
fields: {
author: defineField.text({ required: true }),
quote: defineField.textarea({ required: true }),
rating: defineField.number({ min: 1, max: 5 }),
featured: defineField.boolean({ defaultValue: false })
}
});
Añádela al array collections del mismo archivo:
const collections = [pages, posts, products, media, users, categories, testimonials];
Guarda. El runtime se construye de forma diferida en cada petición, así que la siguiente petición lo
recoge: recarga /admin/collections y ahí está «testimonials», con un formulario funcional de
creación, edición y borrado generado a partir de esas definiciones de campos. No se ha tocado ningún
código del panel de administración.
4. Usarlo mediante HTTP
Las lecturas son abiertas. Las escrituras necesitan un token bearer:
# Log in
TOKEN=$(curl -s -X POST http://localhost:5173/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"forgecms-demo"}' \
| node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).data.token))")
# Create
curl -X POST http://localhost:5173/api/v1/testimonials \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"author":"Ada","quote":"It just works.","rating":5,"featured":true}'
# List, filter, sort, paginate — no token needed
curl "http://localhost:5173/api/v1/testimonials?featured=true&sort=rating&order=desc&limit=10"
Si envías una petición sin author, obtienes un 400 con un error de validación por campo, porque
@forge-cms/core aplica required: true en cada escritura. Si la envías sin la cabecera
Authorization, obtienes un 401.
5. Consultarlo desde el código de servidor
La API HTTP es para los clientes. Desde una ruta de servidor de Analog, sáltatela y llama directamente a la Local API (en inglés):
// apps/www/src/server/routes/api/site/testimonials.get.ts
import { defineEventHandler } from 'h3';
import { getServerRuntime } from '../../../api/runtime';
export default defineEventHandler(async (event) => {
const runtime = await getServerRuntime(event.context.cloudflare?.env);
const { docs } = await runtime.find({
collection: 'testimonials',
where: { featured: true },
sort: 'rating',
order: 'desc',
limit: 3
});
return docs;
});
El mismo pipeline (validación, hooks, acceso, borradores) sin salto HTTP y sin tener que fabricar
ningún Request.
6. Consultarlo desde el navegador
import { Component, inject, signal } from '@angular/core';
import { CmsApiService } from '@forge-cms/angular';
@Component({ selector: 'app-testimonials', template: '…' })
export class TestimonialsComponent {
private readonly cms = inject(CmsApiService);
readonly testimonials = signal<Record<string, unknown>[]>([]);
async ngOnInit() {
const { docs } = await this.cms.listDocuments('testimonials', {
where: { featured: true },
sort: 'rating',
order: 'desc',
limit: 3
});
this.testimonials.set(docs);
}
}
provideForgeCms({ baseUrl: '/api/v1' }) ya está en app.config.ts. Consulta
Angular client (en inglés) para ver la versión de lo mismo basada en signals,
con collectionResource.
Siguientes pasos
- Guía para un proyecto pequeño: usuarios, autenticación con cookies y un panel de administración que funciona, de principio a fin.
- Core concepts (en inglés): qué son realmente el runtime, los adaptadores y la Local API.
- Fields (en inglés): todos los tipos de campo y sus opciones.
- Deployment (en inglés): cómo desplegarlo en Cloudflare con D1 y R2.