This is the Inertia.js server-side adapter for Echo Go web framework.
Inertia.js is a JavaScript library that allows you to build a fully JavaScript-based single-page app without complexity. I assume that you are familiar with Inertia.js and how it works. You also need to familiarize yourself with Echo, a Go web framework. Inertia Echo assists you in developing web applications that leverage both of these technologies.
Table of Contents
- Getting started
- Usage
- Author
- License
In this section, we provide step-by-step instructions on how to get started with Inertia Echo.
Inertia Echo is a Go module that you can install with the following command:
go get github.com/kohkimakimoto/inertia-echo/v2You also need to install Echo like this:
go get github.com/labstack/echo/v4Next, setup the root template that will be loaded on the first page visit to your application. This template should include your site's CSS and JavaScript assets, along with the .inertia and .inertiaHead variables.
In this tutorial, we will create the views/app.html file as the root template.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{- .inertiaHead -}}
</head>
<body>
{{ .inertia }}
{{ vite "js/app.jsx" }}
</body>
</html>Next, you need to implement Go application code with the Echo framework. Create the main.go file with the following code:
package main
import (
"net/http"
inertia "github.com/kohkimakimoto/inertia-echo/v2"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.Logger())
r := inertia.NewHTMLRenderer()
r.MustParseGlob("views/*.html")
r.ViteBasePath = "/build"
r.MustParseViteManifestFile("public/build/manifest.json")
e.Use(inertia.MiddlewareWithConfig(inertia.MiddlewareConfig{
Renderer: r,
}))
e.Use(inertia.CSRF())
e.Static("/", "public")
e.GET("/", func(c echo.Context) error {
return inertia.Render(c, "Index", map[string]any{
"message": "Hello, World!",
})
})
e.Logger.Fatal(e.Start(":8080"))
}Next, you need to setup the frontend of your application. In this tutorial, we will use Vite and React.
If you don't have a package.json file yet, create one with the following command:
npm init -yInstall the required packages:
npm install -D @inertiajs/react react react-dom vite @vitejs/plugin-reactCreate the vite.config.js file with the following content:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
publicDir: false,
build: {
manifest: "manifest.json",
outDir: "public/build",
rollupOptions: {
input: ['js/app.jsx'],
},
},
})Create the js/app.jsx file with the following content:
import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'
createInertiaApp({
resolve: name => {
const pages = import.meta.glob('./pages/**/*.jsx', { eager: true })
return pages[`./pages/${name}.jsx`]
},
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />)
},
})Create a page component as the js/pages/Index.jsx file with the following content:
import React from 'react';
export default function Index({ message }) {
return (
<div>
<h1>{ message }</h1>
</div>
);
}Build the frontend assets with the following command:
npx vite buildNow you can run the application with the following command:
go run .Then, open your browser and navigate to http://localhost:8080.
You should see the message "Hello, World!" displayed on the page.
You can find the complete code of this example in the examples/getting-started directory of this repository.
If you want to run in dev mode so that you can hot-reload frontend updates, set Debug to true.
r := inertia.NewHTMLRenderer()
// ...
r.Debug = true
r.ViteDevServerURL = "http://localhost:5173" // set this to change your Vite server host/port.You need to run Vite server while you running in dev mode. To do so, you also can use kohkimakimoto/go-subprocess to simplify your operation with Vite server.
import (
// import go-subprocess
"github.com/kohkimakimoto/go-subprocess"
)
// ...
func main(){
// ...
e.GET("/", func(c echo.Context) error {
return inertia.Render(c, "Index", map[string]any{
"message": "Hello, World!",
})
})
// Add this to run Vite server.
go func() {
if err := subprocess.Run(&subprocess.Config{
Command: "bun",
Args: []string{"run", "dev"},
Stdout: os.Stdout,
StdoutFormatter: subprocess.PrefixFormatter("[Vite] "),
Stderr: os.Stderr,
StderrFormatter: subprocess.PrefixFormatter("[Vite] "),
Dir: ".",
}); err != nil {
e.Logger.Errorf("the Vite subprocess returned an error: %v", err)
}
}()
e.Logger.Fatal(e.Start(":8080"))
}
Note
If you're using React with @vitejs/plugin-react, you have to add {{ vite_react_refresh }} on your view file as well.
For more information, see Vite docs.
views/app.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{- .inertiaHead -}}
</head>
<body>
{{ .inertia }} {{ vite_react_refresh }} {{ vite "js/app.jsx" }}
</body>
</html>Unlike Laravel, which is an officially supported framework for Inertia.js, Echo lacks built-in view rendering. This means you'll have to build your own view system and integrate it with Inertia.js.
Inertia Echo defines Renderer interface to integrate view system with Inertia.js.
It also provides a built-in renderer implementation based on the html/template package.
To setup Inertia Echo with your Echo application, you need to initialize the renderer and set it up with the middleware.
// Create and configure the renderer...
r := inertia.NewHTMLRenderer()
r.MustParseGlob("views/*.html")
r.ViteBasePath = "/build"
// Setup the middleware with the renderer
e.Use(inertia.MiddlewareWithConfig(inertia.MiddlewareConfig{
Renderer: r,
}))Note
We also officially provide Echo Viewkit renderer as an additional module.
It is a recommended renderer because it provides more powerful Vite support.
For more information see viewkitext module and the example code.
After setting up the renderer, you need to add the Inertia middleware to your Echo application.
e.Use(inertia.MiddlewareWithConfig(inertia.MiddlewareConfig{
Renderer: r,
}))The middleware handles Inertia requests, a foundational functionality of this package.
You can pass a configuration to customize its behavior.
For more details, see the MiddlewareConfig documentation.
π The related official document: Responses
The following code shows how to create an Inertia response.
The Render function accepts a map[string]any as its final argument, which contains the properties to pass to the view.
func ShowEventHandler(c echo.Context) error {
event := // retrieve a event...
return inertia.Render(c, "Event/Show", map[string]any{
"event": event,
})
}You can also pass a struct with the prop struct tag.
type ShowEventProps struct {
Event *Event `prop:"event"`
}
func ShowEventHandler(c echo.Context) error {
event := // retrieve a event...
return inertia.Render(c, "Event/Show", &ShowEventProps{
Event: event,
})
}You can access your properties in the root template.
<meta name="twitter:title" content="{{ .page.Props.event.Title }}">Sometimes you may even want to provide data that will not be sent to your JavaScript component.
In this case, you can use the RenderWithViewData function.
func ShowEventsHandler(c echo.Context) error {
event := // retrieve a event...
return inertia.RenderWithViewData(c, "Event/Show", map[string]any{
"event": event,
}, map[string]interface{}{
"meta": "Meta data...",
})
}You can then access this variable like a regular template variable.
<meta name="twitter:title" content="{{ .meta }}">π The related official document: Redirects
You can use Echo's standard way to redirect.
return c.Redirect(http.StatusFound, "/")The following is a way to redirect to an external website in Inertia apps.
return inertia.Location(c, "/path/to/external")π The related official document: Routing
Inertia Echo provides a helper function for shorthand routes
e.GET("/about", inertia.Handler("About"))π The related official document: Shared data
You can set shared data via middleware.
e.Use(inertia.MiddlewareWithConfig(inertia.MiddlewareConfig{
Share: func(c echo.Context) (map[string]any, error) {
user := // get auth user...
return map[string]any{
"appName": "App Name",
"authUser": user,
}, nil
},
}))Alternatively, you can manually share data using the Share function.
inertia.Share(c, map[string]any{
"appName": "App Name",
"authUser": user,
})π The related official document: Partial reloads
inertia.Render(c, "Users/Index", map[string]any{
// ALWAYS included on standard visits
// OPTIONALLY included on partial reloads
// ALWAYS evaluated
"users": users,
// ALWAYS included on standard visits
// OPTIONALLY included on partial reloads
// ONLY evaluated when needed
"users": func() (any, error) {
users, err := // get users...
if err != nil {
return nil, err
}
return users
},
// NEVER included on standard visits
// OPTIONALLY included on partial reloads
// ONLY evaluated when needed
"users": inertia.Optional(func() (any, error) {
users, err := // get users...
if err != nil {
return nil, err
}
return users, nil
}),
// ALWAYS included on standard visits
// ALWAYS included on partial reloads
// ALWAYS evaluated
"users": inertia.Always(users),
})π The related official document: Deferred props
inertia.Render(c, "Users/Index", map[string]any{
"users": users,
"roles": roles,
"permissions": inertia.Defer(func() (any, error) {
permissions, err := // get permissions...
if err != nil {
return nil, err
}
return permissions, nil
}),
})inertia.Render(c, "Users/Index", map[string]any{
"users": users,
"roles": roles,
"permissions": inertia.Defer(func() (any, error) {
permissions, err := // get permissions...
if err != nil {
return nil, err
}
return permissions, nil
}),
"teams": inertia.DeferWithGroup(func() (any, error) {
teams, err := // get teams...
if err != nil {
return nil, err
}
return teams, nil
}, "attributes"),
"projects": inertia.DeferWithGroup(func() (any, error) {
projects, err := // get projects...
if err != nil {
return nil, err
}
return projects, nil
}, "attributes"),
"tasks": inertia.DeferWithGroup(func() (any, error) {
tasks, err := // get tasks...
if err != nil {
return nil, err
}
return tasks, nil
}, "attributes"),
})π The related official document: Merging props
inertia.Render(c, "Tags/Index", map[string]any{
"tags": inertia.Merge(tags),
})inertia.Render(c, "Users/Index", map[string]any{
"tags": inertia.DeepMerge(users),
})You may chain the matchOn method to determine how existing items should be matched and updated.
inertia.Render(c, "Users/Index", map[string]any{
"tags": inertia.DeepMerge(users).MatchesOn("data.id"),
})π The related official document: CSRF protection
Inertia Echo has CSRF middleware that is configured for Inertia.js.
This middleware provides XSRF-TOKEN cookie and verifies the X-XSRF-TOKEN header in the request.
The following code shows how to set up the CSRF middleware in your Echo application.
e.Use(inertia.CSRF())π The related official document: History encryption
To encrypt a group of routes, you may use EncryptHistoryMiddleware
e.Use(inertia.EncryptHistoryMiddleware())You are able to opt out of encryption on specific pages by calling the EncryptHistory function before returning the response.
inertia.EncryptHistory(c, false)To encrypt the history of an individual request, you can call the EncryptHistory function with true as the second argument.
inertia.EncryptHistory(c, true)inertia.ClearHistory(c)π The related official document: Asset versioning
Configure asset version via middleware.
e.Use(inertia.MiddlewareWithConfig(inertia.MiddlewareConfig{
VersionFunc: func() string { return version },
}))Configure asset version manually.
inertia.SetVersion(c, func() string { return version })π The related official document: Server-side Rendering (SSR)
Inertia Echo supports SSR. See SSR example.
You can bundle frontend builds into single Go binary using embed.
package main
import (
"embed"
"io/fs"
"net/http"
inertia "github.com/kohkimakimoto/inertia-echo/v2"
"github.com/labstack/echo/v4"
)
//go:embed views/*.html
var viewFiles embed.FS
//go:embed public/*
var publicFiles embed.FS
func main() {
e := echo.New()
// ...
r := inertia.NewHTMLRenderer()
r.MustParseFS(viewFiles, "views/*.html")
r.MustParseViteManifestFS(publicFiles, "public/build/manifest.json")
// ...
e.Use(inertia.MiddlewareWithConfig(inertia.MiddlewareConfig{
Renderer: r,
}))
// ...
fsys, err := fs.Sub(publicFiles, "public")
if err != nil {
panic(err)
}
assetHandler := http.FileServer(http.FS(fsys))
e.GET("/*", echo.WrapHandler(http.StripPrefix("/", assetHandler)))
e.GET("/", func(c echo.Context) error {
return inertia.Render(c, "Index", map[string]any{
"message": "Hello, World!",
})
})
e.Logger.Fatal(e.Start(":8080"))
}Kohki Makimoto kohki.makimoto@gmail.com
The MIT License (MIT)