iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
😢

Building a Web Application with Only Markdown and Go

に公開
2021/07/26
1件

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 at http://localhost:8080/user?name=engineer
    Image from Gyazo

  • 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.

gin-gonic/gin - GitHub

Approach

  1. Convert Markdown to HTML (technically Mustache HTML)
  2. Generate display HTML from Mustache HTML
  3. 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!

Image from Gyazo

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.

History of Twin:te - hatenablog

Discussion