Generating CMS from Prisma Schema
Danny Kim
Posted on August 5, 2022
One of Prisma's best features is its TypeScript client generated from Prisma schema. For me, it is a killer feature that differentiates Prisma from other JavaScript ORMs. By using the generated TypeScript client, I don't need to worry about writing custom types no matter how complex my queries are. Even better, Prisma's code generator system is designed to be extensible, so we can make it generate more than just TS client.
Motivated by this flexibility of Prisma generator, I tried to generate a content management system (CMS) from a Prisma schema. This project is still in proof-of-concept stage, but I think it has a potential to be used for building internal admin dashboards.
Existing Projects
There are already content management systems out there that are built on top of Prisma, so let's take a look at some major ones first:
KeystoneJS
KeystoneJS is a headless CMS with a built-in ORM. If you design a database schema using Keystone schema API, this tool generates everything from a GraphQL server to admin UIs. It is an amazing productivity booster. It started to support Prisma as a database adapter from 2021 therefore widened the gap between other headless CMS tools and itself.
Although Keystone is a one of the superior frameworks, it is limited in a sense that it cannot be easily attached to exiting projects because Keystone needs to manage the database itself.
PalJS
PalJS is a GraphQL API + admin UI generation tool built by a solo developer. It covers e2e just like KeystoneJS, but it is much more flexible because PalJS is a Prisma generator! As long as you have a Prisma schema (either prisma db pull
ed or written yourself), you can attach PalJS to any project.
It is a bit difficult to customize the generated UI because everything is wrapped in components.
prisma-generator-proto
So I built a proof-of-concept project called prisma-generator-proto
to overcome the limitations of existing projects.
Making use of Prisma generator interface
Even though some content management systems are standalone services (e.g. Wordpress), others use a CMS for managing data for existing services. Something like Keystone can be good for greenfield projects, but it is challenging to adopt in existing projects. So I wanted prisma-generator-proto
to be a CMS generated from a Prisma schema for ease of adoption.
Providing UI as React hooks
It is definitely convenient to have the entire UI/pages generated like PalJS, most teams need to customize their admin UI as they grow. If you have ever tried to customize components from UI libraries like Material UI, you would know how tricky it is to change behavior of packaged components. So prisma-generator-proto
only exposes data & operations as React hooks without any styling. Developers can build admin UI by mixing these generated hooks with an UI component library.
Prisma generator under the hood
All Prisma generators have a similar mechanism.
Prisma schema
It all starts from a file called schema.prisma
:
datasource db {
url = env("DATABASE_URL")
provider = "postgresql"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String @db.VarChar(255)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
As you can see above, there are three main directives in schema.prisma
:
-
datasource
: Defines how to connect to the database -
model
: Defines each data model -
generator
: Configures a generator that consumes this schema file. By default, we generate TypeScript client withprisma-client-js
generator, and we can declare additional generators usinggenerator
directive again.
schema.prisma
file can either be hand-written or be pulled from existing database with prisma db pull
CLI.
prisma generate
prisma generate
command sequentially runs each generators declared in schema.prisma
. Prisma takes care of parsing the schema, and each generator receives a parsed result called "DMMF". Each DMMF contains the following information as JS objects:
- A list of all models
- A list of fields within each model
- Type, default value, nullability of each field
- Model associations
Also, Prisma lets each generator where to output the generated files, so a generator's job is just to create some files from DMMF and writing them in the designated output directory.
Progress
Here's what prisma-generator-proto
can do now:
- REST API generation
It generates an Express router containing CRUD routes for all models. It does not generate the Express application itself.
Usage:
import express from "express";
import { router } from "../__generated__";
const app = express();
app.use(express.json());
// Use the generated CRUD routes!
app.use("/api", router);
app.listen(3001, () => {
console.log("Listening...");
});
- Client code generation
It generates React hooks that calls the generated REST API routes.
Usage:
import Form from "../components/Form";
import React from "react";
import Table from "../components/Table";
import styles from "../styles/DataScreen.module.css";
import { useCountry } from "../generated/hooks";
const Countries = () => {
// Do CRUD operations with the generated hooks
const [countries, addHandler, formState, setFormState] = useCountry();
return (
<div className={styles.screen}>
<Form
name="Country"
keys={["name", "population", "continentId", "isUnMember"]}
onSubmit={addHandler}
state={formState}
setState={setFormState}
/>
<Table
name="Country"
list={countries}
keys={["id", "name", "population", "continentId", "isUnMember"]}
/>
</div>
);
};
export default Countries;
Try it yourself
prisma-generator-proto is available on NPM.
You can find the source code in this GitHub repository.
Read more & dig deeper
Although what I made is a simple CMS prototype, there are all sorts of crazy generators out there. Check this list out if you want to see more community generators!
Posted on August 5, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.