iTranslated by AI
Building a Web Application with Only Markdown and Go
I wanted to build an app using only Go, so I tried to see if I could create a web app without using HTML or CSS!
There are some limitations regarding flexibility and design, but I think it has become usable enough for a prototype app.
The source code is here (HikaruEgashira/simple-server - GitHub)
-
The page above is displayed with the Markdown below
<!-- pages/user.md -->
# Welcome
{{ text }}
[home](/)
What we'll do this time
- Creating a web app where the HTML parts are replaced with Markdown
- A simple app where entering a name in a form displays the username on the user page
- Using Gin as the server-side framework. It's probably the most famous one.
Approach
- Convert Markdown to HTML (technically Mustache HTML)
- Generate display HTML from Mustache HTML
- Render
Compared to a normal approach,
Step 1 is the only addition, so once you grasp the method, you can apply it in various ways.
Initial Setup
We will use Go modules.
go mod init github.com/xxxxx/yyyyy
go get -u github.com/gin-gonic/gin # Go framework
go get -u github.com/russross/blackfriday # Library for converting Markdown to HTML
go get -u github.com/cbroglie/mustache # Used for inserting variables into Markdown
The directory structure looks like this:
- controller ... Handles processing for each page
- lib ... Creating a library to generate and render HTML from Markdown
- pages ... Markdown files to be rendered
- template ... Contains HTML, but copying and pasting is fine
- usecase ... Location for logic
simple-server ❯❯❯ tree .
.
├── go.mod
├── go.sum
├── main.go
├── controller
│ ├── index.go
│ └── user.go
├── lib
│ └── lib.go
├── pages
│ ├── index.md
│ └── user.md
├── template
│ └── md.html
└── usecase
└── hello.go
Creating a Layout to Apply GitHub Styling to Markdown
It works by copying and pasting.
I'm using a library called github-markdown-css to adjust the appearance without having to touch CSS.
{{define "md"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Markdown Server</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css" integrity="sha512-Oy18vBnbSJkXTndr2n6lDMO5NN31UljR8e/ICzVPrGpSud4Gkckb8yUpqhKuUNoE+o9gAb4O/rAxxw1ojyUVzg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
</head>
<body>
<div class="markdown-body">
{{.}}
</div>
</body>
</html>
{{end}}
Creating Markdown Pages
Since we only use HTML for the form, that is the only part you might need to check.
<!-- pages/index.md -->
# simple-server
### Enter name
<form
action="user"
method="GET"
>
<input type="text" name="name" placeholder="type username">
<input type="submit" value="go">
</form>
I've set it up so that values are embedded using the Mustache format.
You simply write the variable name inside {{ }}.
<!-- pages/user.md -->
# Welcome
{{ text }}
[home](/)
Creating a Render Function to Convert Markdown to HTML
We'll create a library that takes a path as the second argument, and any subsequent arguments as data to be embedded in the Markdown.
// lib/lib.go
package lib
import (
"html/template"
"io"
"io/ioutil"
"github.com/cbroglie/mustache"
"github.com/russross/blackfriday"
)
func Render(w io.Writer, path string, context ...interface{}) {
md, _ := ioutil.ReadFile(path + ".md")
// 1. Convert Markdown to HTML (technically Mustache HTML)
mustacheHtml := string(blackfriday.Run(md))
// 2. Generate display HTML from Mustache HTML
html, _ := mustache.Render(mustacheHtml, context...)
// 3. Render
tmpl := template.Must(template.ParseFiles("template/md.html"))
tmpl.ExecuteTemplate(w, "md", template.HTML(html))
}
// Usage
// Returns HTML with text embedded in pages/user
lib.Render(c.Writer, "pages/user", map[string]string{"text": text})
Routing with Gin
In the main function, we'll use Gin for routing. It's quite simple.
// main.go
package main
import (
"github.com/xxxxx/yyyyy/controller"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", controller.IndexHandler)
router.GET("/user", controller.UserHandler)
router.Run(":8080")
}
Creating Controllers
If you're just returning a static page, a single line is enough.
// controller/index.go
package controller
import (
"github.com/xxxxx/yyyyy/lib"
"github.com/gin-gonic/gin"
)
func IndexHandler(c *gin.Context) {
lib.Render(c.Writer, "pages/index")
}
When embedding values, pass them as arguments.
// controller/user.go
package controller
import (
"github.com/xxxxx/yyyyy/lib"
"github.com/xxxxx/yyyyy/usecase"
"github.com/gin-gonic/gin"
)
func UserHandler(c *gin.Context) {
name := c.Query("name")
text := usecase.Hello(name)
lib.Render(c.Writer, "pages/user", map[string]string{"text": text})
}
In this way, I was able to create the frontend without using HTML!
Summary
While it might be difficult to build a full-scale application with this alone, let's start by making a demo app! I'll share a real example where I turned a server-side only demo into a full application.

Discussion
このマークダウンではソースコードはシンタックスハイライトで表示されますか?