Julien, "NoCMS" built for folks like you and me. It gets the job done without any headache.
I was trying to get a web page up over the weekend, with a simple contact us form, but it required using a php wordpress plugin and start maintaining a yet another php stack, or using hugo and signing up for a subscription service to collect the form data
Most of my clients want a simple webpage and maybe some react with a simple form without needing to signup for yet another subscription service or data collection site just for collecting contact us form data or other simple forms on their respective sites
Though static site generators like Hugo with a mailto, will meet needs of most users, I find myself reaching out to other third party services when I needed a simple form, most of which are moving towards a subscription model or clients/users have to pay with their data(Google forms) and I can only subscribe to so many services before i go bankrupt.
NoCMS, just like other BS words like serverless, nosql, etc...
Julien is the Lemur King in the Animation series All Hail King Julien who sought freedom for himself and other lemurs in his kingdom though he wasn't very bright on how he went about doing this, his intentions were noble.
Run dev
go run . --config=example-julien.yaml --site=example/index.md
Run julien
julien --config=julien.yaml --site=index.md
--host=127.0.0.1
webserver host defaultlocalhost
--port=8080
webserver port default1234
--site=index.md
Site file default 'index.md'--config=julien.yaml
julien config file default 'julien.yaml'
Julien keeps things clean and simple with YAML for configuration. There are five mount location. Here's the lowdown on the essential settings and how to bend them to your will:
# julien.yaml
static:
path: static # Where your static files like images, CSS, and JS hang out
content:
ext: "md" # File extension for your content files (Markdown is the way to go!)
path: "content" # The lair of your website's content
index: "index" # The default filename for index pages (e.g., index.md)
driver: "yaml-md" # How Julien reads and writes your content (stick with the default for now)
assets: [png, jpg, jpeg, gif] # Allowed image file types within your content directories
data:
ext: "md" # Same as content, but for storing submitted form data
path: "data" # Where Julien stashes the goods from your forms
index: "index"
driver: "yaml-md"
forms:
ext: "md" # You guessed it, Markdown for forms too!
path: "forms" # The command center for your website's forms
index: "index"
driver: "yaml-md"
template:
path: templates # The directory where your website's templates reside
name: julien # The chosen one - the template that will bring your website to life
Think of file structure as the blueprint for your website. Here's a breakdown of the key locations and what they do:
- content: This is where you keep your site page contents usually a markdown document with frontmatter for per page configuration, assets are the static files allowed to be served from the page location
-
content/ ├── index.md ├── articles/ │ ├── index.md │ ├── intro-hail-julien.md │ └── all-hail-king-julien/ │ ├── index.md │ └── kings_porait.jpg │ └── contact-us.md
-
forms: This is where you define your website's forms. Each Markdown file in this directory represents a form.
-
data: This is where Julien stores the data submitted through your forms. Each form submission gets its own file in this directory
-
static: This is the home for all your site static assets, like images, CSS files, and JavaScript files
-
template: This is where you store your website's templates. Templates define the look and feel of your website and how your content is displayed
That's it for locations
the contents directory should have and index.md file which will serve as the contents for the home or root page at / get request path are direct mappings to the filenames and other directries within the content directory
views for specific pages can be defined in the frontmatter by the view key if you need to handle the views for a list of files in a directory that can be defined in the directory index.md frontmatter as
# contents/articles/index.md
title: Articles
view: articles
layout: main
page:
view: article
layout: main
The page:
directive in a directory index.md file defines default view and layout for all other markdown documents and sub directories within the current directory. This can be overwritten by the frontmatter of individual page be defining them in the frontmatter of the page like
# contents/about-us.md
view: about-us
layout: main
Each sub directory inherits the view and layout of its parent directory by default if the page:
directive is not found in the parent index file
Forms in Julien are defined in Markdown files within the /forms directory. Here's an example of a simple contact form (contact.md)
# forms/contact-us.md
---
title: Contact Us
driver: yaml-md # Keep it simple, stick with YAML
name: $timestamp # Each submission gets a unique timestamped filename
redirect: /thank-you # Redirect to a thank-you page after successful submission
includes:
ip: ip # Include the user's IP address in the submission data
hostname: hostname # Include the user's hostname
referer: Referer # Include the referring URL
useragent: User-Agent # Include the user's browser information
schema:
name: required,min=1,max=32 # Validation rules for the 'name' field
email: required,email # Validation rules for the 'email' field
message: required,min=10 # Validation rules for the 'message' field
content: message # Optional content field to be extracted from the form and used as the markdown document content
---
Contact me now!
Why wait when we could be building all sort of ideas
Don't wait!
Write your articles in markdown with an editor of your choice. place them in a folder and
thats all to it
-
title: defines the title of the form
-
driver: defines the driver used to dump the contents to the filesystem (stil WIP)
-
name: the file name for each document
$timestamp
uses the request constant timestamp used as the filename -
redirect: The URL to redirect to after a successful form submission.
-
includes: Request runtime values to include in the form data with the keys being the same key to be used and the value is a request value to be extracted and added to the form content before it is written to disk
-
schema: This is the form schema used to validate the form request see validator for other rules just keep it simple and restricted to map validation rules ONLY and you will be fine
-
content: Content key if defined the field will be extracted from the frontmatter and use as the content body in the markdown document and will NOT be in the frontmatter when dumped to disk
Each template directory must include the index.md file at its root with information about the template and configuration Here is sample directory structure of a template named julien:
templates/
│
├── julien/
│ ├── index.md
│ ├── public/
│ │ ├── styles.css
│ │ └── react.js
│ ├── partials/
│ │ ├── header.html
│ │ └── footer.html
│ ├── views/
│ │ ├── 404.html
│ │ ├── 500.html
│ │ ├── index.html
│ │ ├── contact-us.html
│ │ ├── article.html
│ │ └── articles.html
│ └── layouts/
│ ├── main.html
│ └── mobile.html
│
└── mytemplate/
Julien uses the four types of templating systems from which you can choose from or migrate and existing project
-
html: golang official templating system template library and the default templating system
-
pongo: Pongo 2 template engine in the fiber plugin library. I use this engine myself because i already have lost of experience with django templating systems
-
mustache: Mustache templating system template engine
-
jet: Jet templating system template engine
here is a sample template index.md file
# index.md
---
name: Julien
author: 2p4b
layouts: layouts #Path to templates views default to layouts
public: public #Path to templates public assets default to public
views: views #Path to templates views default to views
type: pogo #Template engine to use default to html
ext: html #Templates file extensions default to html
---
Julien Example Template
These variables share a common interface e.g Page.Get("title")
or Site.Get("title", "default")
to get
frontmatter data and Page.Content
for the document body same is true for all data variables
Site
Site documentPage
Current page documentPost
is the data submitted successfully by a formPost.Form
Post Form documentPost.Data
Post Data documentPost.Timestamp
is the unix timestamp of the submission
Template
Current template index.md document
The FormData
variable is populated after a post request with validation errors based on
the form document schema
field
# Example form schema /forms/contact-us.md
schema:
name: required,min=1,max=32 # Validation rules for the 'name' field
email: required,email # Validation rules for the 'email' field
company: required,min=10 # Validation rules for the 'message' field
FormData
Submitted form values with errors onPOST
requestsFormData.Name
Form document nameFormData.Get("company")
Get submitted formcompany
valueFormData.Get("company", "default")
Get submitted formcompany
value or default valueFormData.HasErrors("company")
returns boolean if submitted formcompany
has errorsFormData.HasErrors("company", "min")
returns boolean if submitted formcompany
has error with tag min e.g For form schemaname: required,min=1,max=32
FormData.HasErrors("company", "min", "max")
returns boolean if submitted formcompany
has error with tag min or max e.g For form schemaname: required,min=1,max=32
. can check multiple tagsFormData.GetErrors("company")
Get formcompany
error tagsPost.Timestamp
is the unix timestamp of the submission
Forms
is the mount point for the formsPager
is the mount point for the content
Mounts variables share the common interface of Forms.Find("contact-us")
or Pager.Find("articles/my-article")
and returns a pointer and error. I the document is not found nil/null is return with an error why. If the document is found a pointer to the document data is returned with error set to nil/null.
Another quick way to get a document if you don't need to know the error is to use the Open
method like
Forms.Open("contact-us")
or Pager.Open("articles/my-article")
. The Open
method returns a pointer to the document if
found or nil/null.
- implement sqlite driver
- Optional Frontend ui for the masses