Building a mini CMS with Astro and TypeScript

My goal is to build a template for Astro that works as a composable and flexible mini CMS β€” designed for those who want something lightweight and easy to extend, without relying on too many external libraries.

The result of my experiments is this: simple and somewhat limited in some aspects, but I believe the code can be extended to fit the needs of each project.

demo

source code

It’s becoming increasingly common to rely on multiple JavaScript packages to solve small tasks, but I believe a minimalist approach can also be powerful. Fewer dependencies mean less maintenance and more control over the project.

I think code generators are awesome. A good example is ShadCN, where components can be easily copied and pasted β€” a big part of why it has become so popular. I believe we could bring that same copy-paste logic to CMSs: instead of depending on numerous external packages, it would be more composable and flexible to have the code directly integrated, while keeping things relatively simple.

How do I define a collection?

// collections/brands.ts
import { z } from "zod";
import { brand } from "astro:db";
import type { Collection } from "@/types/collections";

const brandSchema = z.object({
  id: z.string().optional(),
  slug: z.string(),
  name: z.string().min(1),
  description: z.string().optional(),
  website: z.string().optional(),
});

export const brandsCollection = {
  label: "Brands",
  table: brand,
  schema: brandSchema,
  fieldMap: [
    {
      field: "id",
      fieldComponent: "text-field",
      label: "ID",
    },
    {
      field: "name",
      fieldComponent: "text-field",
      required: true,
      label: "Brand Name",
    },
    {
      field: "description",
      fieldComponent: "textarea-field",
      label: "Description",
      placeholder: "Optional brand description",
    },
    {
      field: "slug",
      fieldComponent: "text-field",
      required: true,
      label: "Slug",
      placeholder: "brand-slug",
    },
    {
      field: "website",
      fieldComponent: "url-field",
      label: "Website",
      placeholder: "https://example.com",
    },
  ],
} satisfies Collection<typeof brandSchema>;