Mastodon hachyterm.io

The Problem

Let’s say that you create a simple web application with the Nim Heroku Buildpack.

Project structure:

├── nim_heroku.nimble
├── Procfile
├── src
│   ├── nim_heroku
│   ├── nim_heroku.nim
│   └── views
│       └── general.nim
├── static_dir
│   ├── favicon.ico
│   └── style.css
└── tags

3 directories, 8 files

Bin file (src/nim_heroku.nim):

import jester, asyncdispatch, os, strutils
import views/general

var settings = newSettings()

if existsEnv("PORT"):
  settings.port = Port(parseInt(getEnv("PORT")))

settings.staticDir = "./static_dir"

routes:
  get "/":
    resp renderMain()

runForever()

View (src/views/general.nim):

#? stdtmpl(subsChar = '$', metaChar = '#')
#
#proc renderMain*(): string =
##  result = ""
<!DOCTYPE html>
<html>
  <head>
    <title>Nim Heroku Test</title>
    <link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css" />
    <link rel="stylesheet" type="text/css" href="style.css">
    <link rel="shortcut icon" href="favicon.ico">
  </head>
  <body>
    <div class="center text-center">
      <div class="[ flow ]">
        <p>Hello World</p>
        <p>This is a test</p>
      </div>
    </div>
  </body>
</html>
#end proc

We load a css file and a favicon in the HTML head.

That works fine in local development. But when you deploy to Heroku, you won’t see those files with a HTTP status error code of 403.

The Solution

You can bundle assets like CSS or JavaScript with nimassets.

  1. Install nimassets locally

    nimble install nimassets
    
  2. Use your terminal to create the assets file:

    nimassets -d=static_dir -o= src/views/assetsfile.nim
    
  3. Import the assets file into your view. Add the CSS file to your HTML with the <style> tag.

    #? stdtmpl(subsChar = '$', metaChar = '#')
    #import assetsfile
    #
    #let css = assetsFile.getAsset("static_dir/style.css")
    #
    #proc renderMain*(): string =
    ##  result = ""
    <!DOCTYPE html>
    <html>
      <head>
        <title>Nim Heroku Test</title>
        <link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css" />
        <style type="text/css">
          ${css}
        </style>
        <link rel="shortcut icon" href="favicon.ico">
      </head>
      <body>
        <div class="center text-center">
          <div class="[ flow ]">
            <p>Hello World</p>
            <p>This is a test</p>
          </div>
        </div>
      </body>
    </html>
    #end proc
    
  4. Serve the favicon icon directly with Jester. You have to find the file with os.walkdir (src/views/static_assets.nim):

    import os, sequtils, re
    
    var filepaths: seq[string]
    
    for kind, path in walkdir("./static_dir/"):
      filepaths.add(path)
    
    let icoFile = filter(filepaths, proc (x: string): bool =
      contains(x, re".ico"))[0]
    
    let ico* = readFile(icoFile)
    
  5. Create a route for serving the favicon:

    import jester, asyncdispatch, os, strutils
    import views/general, views/static_assets
    
    var settings = newSettings()
    
    settings.staticDir = "./static_dir"
    
    if existsEnv("PORT"):
      settings.port = Port(parseInt(getEnv("PORT")))
    
    
    template corsResp(code, message: untyped): untyped =
      mixin resp
      resp code, {"Access-Control-Allow-Origin": "*"}, message
    
    routes:
      get "/":
        corsResp(Http200, renderMain())
    
      get "/favicon.ico":
        corsResp(Http200, ico)
    
    runForever()
    
  6. Push to Heroku and it should work.

Project structure:

.
├── nim_heroku.nimble
├── Procfile
├── src
│   ├── nim_heroku
│   ├── nim_heroku.nim
│   └── views
│       ├── assetsfile.nim
│       ├── general.nim
│       ├── static_assets
│       └── static_assets.nim
├── static_dir
│   ├── favicon.ico
│   └── style.css
└── tags

3 directories, 11 files

Further Reading