Skip to content

Commit

Permalink
Add Embedded KeystoneJS example
Browse files Browse the repository at this point in the history
  • Loading branch information
jesstelford committed May 22, 2021
1 parent 7035a03 commit 0b4de4e
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 0 deletions.
36 changes: 36 additions & 0 deletions examples/cms-keystonejs-embedded/.gitignore
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# KeystoneJS
.keystone

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
71 changes: 71 additions & 0 deletions examples/cms-keystonejs-embedded/README.md
@@ -0,0 +1,71 @@
# Embedded KeystoneJS Example

A Static Blog starter project powered by [KeystoneJS](https://keystonejs.com):

- Powerful Admin UI for creating & editing content in dev mode
- Statically built pages for fast production sites
- Client-side access to data via auto-generated GraphQL API

## Deploy on Vercel

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-keystonejs-embedded&project-name=cms-keystonejs-embedded&repository-name=cms-keystonejs-embedded)

## How to use

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
npx create-next-app --example cms-keystonejs-embedded cms-keystonejs-embedded
# or
yarn create next-app --example cms-keystonejs-embedded cms-keystonejs-embedded
```

Next, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the Next.js site, and [http://localhost:8000](http://localhost:8000) to see the KeystoneJS Admin UI.

Make changes in the KeystoneJS Admin UI, then reload the Next.js site to see what it looks like!

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

## KeystoneJS

[KeystoneJS](https://keystonejs.com) is the best headless content management system around, especially when you're using a component-based front-end like React and Vue. In addition to auto-generating a GraphQL API and providing a powerful Admin UI, KeystoneJS is backed by Prisma, so can work with a range of different databases.

This example uses a local SQLite database (a `.db` file) to store data. In development mode, the KeystoneJS Admin UI will save data to the local SQLite database file, and in production the KeystoneJS GraphQL & node APIs will read data from it.

NOTE: The local SQLite database must be deployed along with the rest of the code in this example. Usually this means committing it to source control. Due to this reason, it is not recommended to store any private data using this example repo.

For more details, see [How to embed Keystone + SQLite in a Next.js app](https://next.keystonejs.com/tutorials/embedded-mode-with-sqlite-nextjs)

## The code

The key files for this project

```
.
├─ app.db # The SQLite database. Commit this with your changes
├─ keystone.ts # Configure Keystone's data model & other options
└─ pages
  ├─ api
  │  └─ graphql.tsx # Access your data via a GraphQL API
  └─ post
   └─ [slug].tsx # Statically generate pages based on your data
```

## Learn More

To learn more about KeystoneJS, take a look at the following resources:

- [KeystoneJS Documentation](https://keystonejs.com) - learn about KeystoneJS features and API.
- [How to embed Keystone + SQLite in a Next.js app](https://next.keystonejs.com/tutorials/embedded-mode-with-sqlite-nextjs) - the tutorial which inspired this example

You can check out [the KeystoneJS GitHub repository](https://github.com/keystonejs/keystone) - your feedback and contributions are welcome!
19 changes: 19 additions & 0 deletions examples/cms-keystonejs-embedded/keystone.ts
@@ -0,0 +1,19 @@
import { config, list } from '@keystone-next/keystone/schema'
import { text } from '@keystone-next/fields'

const Post = list({
fields: {
title: text({ isRequired: true }),
slug: text(),
content: text(),
},
})

export default config({
db: { provider: 'sqlite', url: 'file:./app.db' },
experimental: {
generateNextGraphqlAPI: true,
generateNodeAPI: true,
},
lists: { Post },
})
3 changes: 3 additions & 0 deletions examples/cms-keystonejs-embedded/next.config.js
@@ -0,0 +1,3 @@
const { withKeystone } = require('@keystone-next/keystone/next')

module.exports = withKeystone()
22 changes: 22 additions & 0 deletions examples/cms-keystonejs-embedded/package.json
@@ -0,0 +1,22 @@
{
"name": "test-embedded-keystone",
"version": "0.1.0",
"private": true,
"scripts": {
"postinstall": "keystone-next postinstall",
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@keystone-next/fields": "^9.0.0",
"@keystone-next/keystone": "^18.0.0",
"next": "10.2.2",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"@types/react": "^17.0.6",
"typescript": "^4.2.4"
}
}
7 changes: 7 additions & 0 deletions examples/cms-keystonejs-embedded/pages/_app.tsx
@@ -0,0 +1,7 @@
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp
1 change: 1 addition & 0 deletions examples/cms-keystonejs-embedded/pages/api/graphql.tsx
@@ -0,0 +1 @@
export { default, config } from '.keystone/next/graphql-api'
63 changes: 63 additions & 0 deletions examples/cms-keystonejs-embedded/pages/index.tsx
@@ -0,0 +1,63 @@
import Head from 'next/head'
import Link from 'next/link'
import styles from '../styles/Home.module.css'

// Import the generated Lists API from Keystone
import { lists } from '.keystone/api'

// Home receives a `posts` prop from `getStaticProps` below
export default function Home({ posts }) {
return (
<div className={styles.container}>
<Head>
<title>Embedded KeystoneJS example</title>
<meta
name="description"
content="A fully embedded KeystoneJS instance within a Next.js app"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Embedded <a href="https://keystonejs.com">KeystoneJS</a> example
</h1>
<ul className={styles.grid}>
{posts.length ? (
/* Render each post with a link to the content page */
posts.map((post) => (
<li key={post.id} className={styles.card}>
<Link href={`/post/${post.slug}`}>
<a>
<h2>{post.title}</h2>
<p>Read now &rarr;</p>
</a>
</Link>
</li>
))
) : (
<li className={`${styles.noposts} ${styles.card}`}>
<Link href="http://localhost:8000">
<a>
<h2>No Posts</h2>
<p>Add one via the Admin UI &rarr;</p>
</a>
</Link>
</li>
)}
</ul>
{process.env.NODE_ENV !== 'production' && (
<Link href="/api/graphql?query=%7BallPosts%7Btitle%2Cslug%2Ccontent%7D%7D">
<a className={styles.playground}>Visit the graphql playground</a>
</Link>
)}
</main>
</div>
)
}

// Here we use the Lists API to load all the posts we want to display
// The return of this function is provided to the `Home` component
export async function getStaticProps() {
const posts = await lists.Post.findMany({ query: 'id title slug' })
return { props: { posts } }
}
42 changes: 42 additions & 0 deletions examples/cms-keystonejs-embedded/pages/post/[slug].tsx
@@ -0,0 +1,42 @@
import Link from 'next/link'
import { lists } from '.keystone/api'

export default function Home({ post }) {
return (
<div>
<main style={{ margin: '3rem' }}>
<div>
<Link href="/">
<a>&larr; back home</a>
</Link>
</div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
</div>
)
}

export async function getStaticPaths() {
const posts = await lists.Post.findMany({
query: `slug`,
})

const paths = posts
.map((post) => post.slug)
.filter((slug): slug is string => !!slug)
.map((slug) => `/post/${slug}`)

return {
paths,
fallback: false,
}
}

export async function getStaticProps({ params: { slug } }) {
const [post] = await lists.Post.findMany({
where: { slug },
query: 'id title content',
})
return { props: { post } }
}
Binary file not shown.
94 changes: 94 additions & 0 deletions examples/cms-keystonejs-embedded/styles/Home.module.css
@@ -0,0 +1,94 @@
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}

.main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.title a {
text-decoration: none;
background-image: linear-gradient(to right, #0ea5e9, #4f46e5);
background-clip: text;
font-weight: 600;
color: transparent;
}

.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}

.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
text-align: center;
vertical-align: middle;
}

.playground {
margin-top: 2rem;
}

.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
list-style: none;
padding: 0;
width: 100%;
}

.card {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
width: 45%;
}

.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}

.card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}

.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}

.card.noposts {
opacity: 0.6;
}

.card.noposts:hover {
opacity: 1;
}
20 changes: 20 additions & 0 deletions examples/cms-keystonejs-embedded/styles/globals.css
@@ -0,0 +1,20 @@
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
color: inherit;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

* {
box-sizing: border-box;
}

0 comments on commit 0b4de4e

Please sign in to comment.