NOTE: This pkg is no longer being actively maintained, if you'd like to maintain this pkg please express interest by opening an issue.
Write markdown, generate GraphQL TypesDefs & Resolvers, query via GraphQL, and serve as html.π₯
GraphQL Markdown is a simple library that parses and converts your .md
files into html
and automatically generates GraphQL FieldDefinitions
from its frontMatter content which can then be queried via a GraphQL server.π₯
After generating the FieldDefinitions
, we save the processed data to an in-memory db. We export the generated TypeDefs
and Resolvers
to enable you to have complete control over creating your own GraphQL Schema.π
A simple GraphQL API for querying your processed content is provided. Which includes a way to sort
, limit
, and skip
your results enabling a basic form of pagination. πΎ
NOTE: We are looking for feedback & suggestions on how to improve and further develop this pkg.
Please feel free to open an issue!
# With npm
npm install --save https://github.com/okgrow/graphql-markdown
# With Yarn
yarn add https://github.com/okgrow/graphql-markdown
---
id: myFirstMdFile
groupId: homePage
---
# Hello World!
Welcome to this pale blue dot!
import express from 'express';
import graphqlHTTP from 'express-graphql';
import { makeExecutableSchema } from 'graphql-tools';
import { runGraphqlMarkdown } from '@okgrow/graphql-markdown';
// Create our options for processing the markdown & images.
const options = {
contentRoot: `${__dirname}/content`,
};
const app = express();
(async () => {
try {
const {
typeDefs,
resolvers,
fileCount, // num of files processed
} = await runGraphqlMarkdown(options);
console.log(
`Loaded \n${fileCount} ContentItems into our in memory DB!`,
);
const schema = makeExecutableSchema({
typeDefs: typeDefs,
resolvers: resolvers,
});
app.use(
'/graphiql',
graphqlHTTP({
schema,
graphiql: true,
}),
);
// Start the server after all data has loaded.
app.listen(4000);
console.log('Server Started! http://localhost:4000/graphiql');
} catch (error) {
console.error('[runGraphqlMarkdown]', error);
}
})();
Below are all the options you may pass to runGraphqlMarkdown
.
const options = {
contentRoot: `fullpath/to/root/of/content`, // required
imageResolver: ({ imgPath, contentRoot }) => {...}, // optional
replaceContents: ({ contentRoot, rawContents }) => {...}, // optional
imageFormats: '(png|svg)', // optional
debugMode: true, // optional
syntaxHighlighter: (code) => {...}, // optional
};
The fullpath of where your .md
content is located.
imageResolver
expects a function that will return the URI for the image. By default images are converted to base64 if no function is assigned to imageResolver.
NOTE: You shouldn't use base64 in production as it increases the size of images significantly.
// Simple example of a imageResolver function.
const serveImagesFromServer = ({ imgPath, contentRoot }) =>
`/images${imgPath.slice(contentRoot.length)}`;
replaceContents
is an optional function that if provided will be called against your .md
content in order to replace the static content at run time. See below example for usage & signature.
const replaceWords = ({ contentRoot, rawContents }) =>
rawContents.replace(new RegExp('deployment-server', 'g'), `${isProduction ? 'production' : 'development'}`);
Image formats that we should process. If not provided we will use the DEFAULT_SUPPORTED_IMAGE_FORMATS
.
imageFormats
expects a string in the following format '(ext|ext|ext|..etc)', e.g '(png|svg)'
.
To enable additional logging during development. Default is false
.
syntaxHighlighter
can be used to provide runGraphqlMarkdown
with a function to syntax highlight your content. Simple example below using highlight.js
.
import hljs from 'highlight.js'; // Only install/use if you want to highlight code.
const yourCodeHighlighter = (code) => hljs.highlightAuto(code).value;
GraphQL Markdown provides a few different approaches to querying the data extracted from your .md
files.
- πͺΒ A powerful & all purpose query: Search by logical
AND
,OR
conditions on any fields!!! π
contentItems(filter: FilterFields!, pagination: Pagination): [ContentItem!]
- πΒ Simplified helper queries: providing a clean & crisp syntax for common querying patterns. π
contentItemById(id: ID!): ContentItem
contentItemsByIds(ids: [ID!]!, pagination: Pagination): [ContentItem!]
contentItemsByGroupId(groupId: ID!, pagination: Pagination): [ContentItem!]
- πΒ Organise the query results: Simple syntax to
sort
,skip
, andlimit
your results. π
enum OrderBy {
ASCENDING
DESCENDING
}
# Sort results by a specific field and order in Ascending or Descending order.
# e.g -> { sortBy: "date", orderBy: "DESCENDING" }
input Sort {
sortBy: String! # Field to sort by. e.g -> "date"
orderBy: OrderBy! # ASCENDING or DESCENDING order. e.g -> "DESCENDING"
}
input Pagination {
sort: Sort # Sort and order elements by a specific field in a specific order.
skip: Int # Do not return the first x elements.
limit: Int # Limit the number of elements to return.
}
A markdown file contains two distinct sections, the FrontMatter section and the Markdown section.
---
FrontMatter Section
---
Markdown Section
The FrontMatter section contains key
:value
pairs. Every Markdown file is required to contain a FrontMatter section and must contain an id
and an groupId
key-value pair. You can add as many additional key
:value
pairs as you like, we will generate GraphQL Field Definitions
from these additional key
:value
at runtime. Check out the typeDefs.graphql file to see where we inject these Field Definitions
.
---
id: myFirstMdFile
groupId: homePage
---
The Markdown section is placed after the FrontMatter section and contains your markdown content. The markdown content will be converted into valid HTML when we process all markdown files and store them in memory.
# My First Markdown file
Hello World!
---
id: home-content-section-1
groupId: homePage
type: pageContent
title: Wonder Website
description: Wonder Website - Home Page
date: "2017-12-25"
tags: [Happy, Learnings]
---
# Welcome to this wonderful website!
Hello world!
Thanks for dropping by to say hello! π₯π₯π₯
Run the simple-example found in examples/simple
, and copy/paste the below snippet into GraphiQL to see the response yourself!π₯
{
# Simplified helper query to search for a single ContentItem.
# Returns a ContentItem, else null if not found.
contentItemById(id: "homePage") {
html
description
}
# Simplified helper query to search for ContentItems by ids.
# Returns a List of contentItems, else empty List if none found.
# Supports Pagination (sort, skip, limit).
contentItemsByIds(ids: ["graphqlIntro", "homePage"]) {
id
tags
order
}
# Simplified helper query to search for ContentItems by groupId.
# Return a List of contentItems, else empty List if none found.
# Supports Pagination (sort, skip, limit).
contentItemsByGroupId(
groupId: "simple-example"
pagination: {
sort: {
sortBy: "order",
orderBy: DESCENDING
}
skip: 0
limit: 0
}
) {
id
order
groupId
}
# Full powered query to search for ContentItems by any field!!!
# Supports searching by logical AND, OR conditions on any fields.
# Return a List of contentItems, else empty List if none found.
# Supports Pagination (sort, skip, limit).
contentItems(
filter: {
AND: {
order: 2,
groupId: "simple-example"
}
}
pagination: {
sort: {
sortBy: "order",
orderBy: ASCENDING
}
}
) {
id
type
date
groupId
description
}
}
This example will be showing all the possible options and features of this package.
// server/index.js
import hljs from 'highlight.js'; // Only install/use if you want to highlight code.
import { makeExecutableSchema } from 'graphql-tools';
import { runGraphqlMarkdown } from '@okgrow/graphql-markdown';
const isProduction = process.env.NODE_ENV === 'production';
// Simple example of imageResolver.
const serveImagesFromServer = ({ imgPath, contentRoot }) =>
`/images${imgPath.slice(contentRoot.length)}`;
const replaceWords = ({ contentRoot, rawContents }) =>
rawContents.replace(new RegExp('deployment-server', 'g'), `${isProduction ? 'production' : 'development'}`);
// NOTE: Simple example of highlighting code by using highlight.js. You can use
// any highlighter you like that conforms to the below function signature.
const yourCodeHighlighter = (code) => hljs.highlightAuto(code).value;
// Create our options for loading the markdown into our in memory db.
// NOTE: By default images are converted to base64 if no function is passed to imageResolver.
// NOTE: You shouldn't use base64 in production as it increases the size of images significantly.
// NOTE: imageFormats is optional, if not provided it will use the DEFAULT_SUPPORTED_IMAGE_FORMATS.
// imageFormats expects a string in the following format '(ext|ext|ext|..etc)'
const options = {
contentRoot: `fullpath/to/root/of/content`, // required
imageResolver: isProduction ? serveImagesFromServer : null, // optional
replaceContents: replaceWords, // optional
imageFormats: '(png|svg)', // optional
debugMode: true, // optional
syntaxHighlighter: yourCodeHighlighter, // optional
},
};
(async () => {
try {
// Find all markdown files, process and load them into memory and
// return the TypeDefs & Resolvers for the graphql-markdown pkg.
const {
typeDefs,
resolvers,
fileCount,
} = await runGraphqlMarkdown(options);
console.log(`DB ready!\n${fileCount} ContentItems loaded!`);
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// Now start your GraphQL Server using the schema you just created.
// If your unsure how to do this check out the examples dir.
} catch (error) {
console.error('[runGraphqlMarkdown]', error);
}
})();
# clone the repo
git clone [email protected]:okgrow/graphql-markdown.git
# Don't forget to install
npm install
# Run all tests (We use Jest)
npm test
Check out the examples folder to see how it all works. Please note:
- Node version 8+ is required.
- You must run
npm install
on the main package first as the examples import the/dist
files. - Examples contain detailed instructions & example queries to copy paste into Graphiql.
This is an open source package. We hope to deal with contributions in a timely manner, but that's not always the case. The main maintainers are:
Feel free to ping if there are open issues or pull requests which are taking a while to be dealt with!
Issues and Pull Requests are always welcome.
Please read our contribution guidelines.
If you are interested in becoming a maintainer, get in touch with us by sending an email or opening an issue. You should already have code merged into the project. Active contributors are encouraged to get in touch.
Please note that all interactions in @okgrow's repos should follow our Code of Conduct.
Released under the MIT license Β© 2017 OK GROW!.