Docs / Programming & Development / KeystoneJS Application

KeystoneJS Application

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 220 views · 3 min read

KeystoneJS is a powerful headless CMS and web application framework that generates a GraphQL API and admin UI from your schema definition. Built on Prisma and GraphQL, it provides type-safe database access and a beautiful admin interface. This guide covers deploying KeystoneJS on a VPS.

Project Setup

# Create KeystoneJS project
npm init keystone-app@latest my-keystone
cd my-keystone

# Select starter project or blank

Schema Definition

// keystone.ts
import { config, list } from "@keystone-6/core";
import { allowAll } from "@keystone-6/core/access";
import { text, relationship, password, timestamp, select, integer, checkbox } from "@keystone-6/core/fields";
import { document } from "@keystone-6/fields-document";

const lists = {
  User: list({
    access: allowAll,
    fields: {
      name: text({ validation: { isRequired: true } }),
      email: text({ isIndexed: "unique", validation: { isRequired: true } }),
      password: password({ validation: { isRequired: true } }),
      posts: relationship({ ref: "Post.author", many: true }),
    },
  }),

  Post: list({
    access: allowAll,
    fields: {
      title: text({ validation: { isRequired: true } }),
      slug: text({ isIndexed: "unique" }),
      status: select({
        options: [
          { label: "Draft", value: "draft" },
          { label: "Published", value: "published" },
        ],
        defaultValue: "draft",
        ui: { displayMode: "segmented-control" },
      }),
      content: document({
        formatting: true,
        dividers: true,
        links: true,
        layouts: [
          [1, 1], [1, 1, 1],
        ],
      }),
      author: relationship({ ref: "User.posts" }),
      publishDate: timestamp(),
    },
  }),

  Tag: list({
    access: allowAll,
    fields: {
      name: text(),
      posts: relationship({ ref: "Post", many: true }),
    },
  }),
};

export default config({
  db: {
    provider: "postgresql",
    url: process.env.DATABASE_URL || "postgres://localhost/keystone",
  },
  lists,
  server: {
    port: parseInt(process.env.PORT || "3000"),
    cors: {
      origin: [process.env.FRONTEND_URL || "http://localhost:7777"],
    },
  },
});

Building and Running

# Development
npm run dev

# Build for production
npm run build

# Start production
npm start

# Push schema to database
npm run build -- --db-push

GraphQL API

# Auto-generated GraphQL at /api/graphql

# List posts
query {
  posts(where: { status: { equals: "published" } }, orderBy: { publishDate: desc }) {
    id
    title
    slug
    content { document }
    author { name }
  }
}

# Create post
mutation {
  createPost(data: {
    title: "My First Post"
    slug: "my-first-post"
    status: "published"
    content: { document: [...] }
  }) {
    id
    title
  }
}

Docker Deployment

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.keystone ./.keystone
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/schema.prisma ./
COPY package*.json keystone.ts ./
USER node
EXPOSE 3000
CMD ["npm", "start"]

Systemd Service

[Unit]
Description=KeystoneJS Application
After=network.target postgresql.service

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/keystone
ExecStart=/usr/bin/npm start
EnvironmentFile=/opt/keystone/.env
Environment=NODE_ENV=production
Restart=always

[Install]
WantedBy=multi-user.target

Summary

KeystoneJS provides an elegant code-to-API experience: define your schema in TypeScript, and get a GraphQL API, admin UI, and Prisma database layer automatically. The document field provides rich content editing, relationships handle complex data models, and access control secures everything. Self-hosting on a VPS gives you a powerful, customizable CMS and API backend with full data ownership.

Was this article helpful?