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

![Demo 1](/assets/articles/build-a-mini-cms-template-for-astro/image.webp)

![Demo 2](/assets/articles/build-a-mini-cms-template-for-astro/image%20(1).webp)

![Demo 3](/assets/articles/build-a-mini-cms-template-for-astro/image%20(3).webp)

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>;