Skip to content

kohkimakimoto/inertia-echo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

169 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Inertia Echo

test MIT License Go Reference

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

In this section, we provide step-by-step instructions on how to get started with Inertia Echo.

Installation

Inertia Echo is a Go module that you can install with the following command:

go get github.com/kohkimakimoto/inertia-echo/v2

You also need to install Echo like this:

go get github.com/labstack/echo/v4

Root template

Next, 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>

Write Go code

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"))
}

Setup frontend

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 -y

Install the required packages:

npm install -D @inertiajs/react react react-dom vite @vitejs/plugin-react

Create 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 build

Run the application

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

Run in Dev mode

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>

Usage

Renderer

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.

Middleware

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.

Responses

πŸ“– The related official document: Responses

Creating 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,
	})
}

Creating responses using structs

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,
	})
}

Root template data

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 }}">

Redirects

πŸ“– The related official document: Redirects

You can use Echo's standard way to redirect.

return c.Redirect(http.StatusFound, "/")

External redirects

The following is a way to redirect to an external website in Inertia apps.

return inertia.Location(c, "/path/to/external")

Routing

πŸ“– The related official document: Routing

Shorthand routes

Inertia Echo provides a helper function for shorthand routes

e.GET("/about", inertia.Handler("About"))

Shared data

πŸ“– The related official document: Shared data

Sharing data using middleware

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
	},
}))

Sharing data manually

Alternatively, you can manually share data using the Share function.

inertia.Share(c, map[string]any{
	"appName":  "App Name",
	"authUser": user,
})

Partial reloads

πŸ“– 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),
})

Deferred props

πŸ“– 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
	}),
})

Grouping requests

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"),
})

Merging props

πŸ“– The related official document: Merging props

Shallow merge

inertia.Render(c, "Tags/Index", map[string]any{
	"tags": inertia.Merge(tags),
})

Deep merge

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"),
})

CSRF protection

πŸ“– 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())

History encryption

πŸ“– The related official document: History encryption

Encrypt middleware

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)

Per-request encryption

To encrypt the history of an individual request, you can call the EncryptHistory function with true as the second argument.

inertia.EncryptHistory(c, true)

Clearing history

inertia.ClearHistory(c)

Asset versioning

πŸ“– 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 })

Server-side Rendering (SSR)

πŸ“– The related official document: Server-side Rendering (SSR)

Inertia Echo supports SSR. See SSR example.

Embed

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"))
}

Author

Kohki Makimoto kohki.makimoto@gmail.com

License

The MIT License (MIT)

About

The Inertia.js server-side adapter for Echo Go web framework.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors