gonertia

package module
v2.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2024 License: MIT Imports: 17 Imported by: 0

README

Gonertia

gonertia

Gonertia is a well-tested and zero-dependency Inertia.js server-side adapter for Golang. Visit inertiajs.com to learn more.

Latest Release Audit Workflow Go Report Card Go Reference MIT license

Introduction

Inertia allows you to create fully client-side rendered single-page apps without the complexity that comes with modern SPAs. It does this by leveraging existing server-side patterns that you already love.

This package based on the official Laravel adapter for Inertia.js inertiajs/inertia-laravel, supports all the features and works in the most similar way.

Roadmap

  • Tests
  • Helpers for testing
  • Helpers for validation errors
  • Examples
  • SSR
  • Inertia 2.0 compatibility

Installation

Install using go get command:

go get github.com/romsar/gonertia/v2

Usage

Basic example

Initialize Gonertia in your main.go:

package main

import (
    "log"
    "net/http"

    inertia "github.com/romsar/gonertia"
)

func main() {
    i, err := inertia.New(rootHTMLString)
    // i, err := inertia.NewFromFile("resources/views/root.html")
    // i, err := inertia.NewFromFileFS(embedFS, "resources/views/root.html")
    // i, err := inertia.NewFromReader(rootHTMLReader)
    // i, err := inertia.NewFromBytes(rootHTMLBytes)
    if err != nil {
        log.Fatal(err)
    }

    // Now create your HTTP server.
    // Gonertia works well with standard http server library,
    // but you are free to use some external routers like Gorilla Mux or Chi.
    mux := http.NewServeMux()

    mux.Handle("/home", i.Middleware(homeHandler(i)))
}

func homeHandler(i *inertia.Inertia) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        err := i.Render(w, r, "Home/Index", inertia.Props{
            "some": "data",
        })

        if err != nil {
            handleServerErr(w, err)
            return
        }
    }

    return http.HandlerFunc(fn)
}

Create root.html template:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<!-- Put here your styles, meta and other stuff -->
		{{ .inertiaHead }}
	</head>

	<body>
		{{ .inertia }}
		<script type="module" src="/build/assets/app.js"></script>
	</body>
</html>
Starter kits
  1. Gonertia + Vue + Vite + Tailwind
  2. Gonertia + Svelte + Vite + Tailwind
  3. Gonertia + React + Vite + Tailwind
  4. Also you can use Alpacaproj project generator
More examples
Set asset version (learn more)
i, err := inertia.New(
    /* ... */
    inertia.WithVersion("some-version"), // by any string
    inertia.WithVersionFromFile("./public/build/manifest.json"), // by file checksum
    inertia.WithVersionFromFileFS(embedFS, "./public/build/manifest.json"), // by file checksum from fs.FS
)
SSR (Server Side Rendering) (learn more)

To enable server side rendering you have to provide an option in place where you initialize Gonertia:

i, err := inertia.New(
/* ... */
    inertia.WithSSR(), // default is http://127.0.0.1:13714
    inertia.WithSSR("http://127.0.0.1:1234"), // custom url http://127.0.0.1:1234
)

Also, you have to use asset bundling tools like Vite or Webpack (especially with Laravel Mix). The setup will vary depending on this choice, you can read more about it in official docs or check an example that works on Vite.

Optional and Always props (learn more)
props := inertia.Props{
    "optional": inertia.Optional{func () (any, error) {
        return "prop", nil
    }},
    "always": inertia.Always("prop"),
}

i.Render(w, r, "Some/Page", props)
Merging props (learn more)
props := inertia.Props{
    "merging": inertia.Merge([]int{rand.Int63()}),
}
Deferred props (learn more)
props := inertia.Props{
    "defer_with_default_group": inertia.Defer(func () (any, error) {
        return "prop", nil
    }),
    "defer_with_custom_group": inertia.Defer("prop", "foobar"),
    "defer_with_merging": inertia.Defer([]int64{rand.Int63()}).Merge(),
}
Redirects (learn more)
i.Redirect(w, r, "https://example.com") // plain redirect
i.Location(w, r, "https://example.com") // external redirect

NOTES: If response is empty - user will be redirected to the previous url, just like in Laravel official adapter.

To manually redirect back, you can use Back helper:

i.Back(w, r)
Share template data (learn more)
i.ShareTemplateData("title", "Home page")
<h1>{{ .title }}</h1>
Share template func
i.ShareTemplateFunc("trim", strings.TrimSpace)
<h1>{{ trim " foo bar " }}</h1>
Pass template data via context (in middleware)
ctx := inertia.SetTemplateData(r.Context(), inertia.TemplateData{"foo", "bar"})
// or inertia.SetTemplateDatum(r.Context(), "foo", "bar")

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
Share prop globally (learn more)
i.ShareProp("foo", "bar")
Pass props via context (in middleware)
ctx := inertia.SetProps(r.Context(), inertia.Props{"foo": "bar"})
// or inertia.SetProp(r.Context(), "foo", "bar")

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
Validation errors (learn more)
ctx := inertia.SetValidationErrors(r.Context(), inertia.ValidationErrors{"some_field": "some error"})
// or inertia.AddValidationErrors(r.Context(), inertia.ValidationErrors{"some_field": "some error"})
// or inertia.SetValidationError(r.Context(), "some_field", "some error")

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
Replace standard JSON marshaller
  1. Implement JSONMarshaller interface:
import jsoniter "github.com/json-iterator/go"

type jsonIteratorMarshaller struct{}

func (j jsonIteratorMarshaller) Decode(r io.Reader, v any) error {
    return jsoniter.NewDecoder(r).Decode(v)
}

func (j jsonIteratorMarshaller) Marshal(v any) ([]byte, error) {
    return jsoniter.Marshal(v)
}
  1. Provide your implementation in constructor:
i, err := inertia.New(
    /* ... */,
    inertia.WithJSONMarshaller(jsonIteratorMarshaller{}),
)
Use your logger
i, err := inertia.New(
    /* ... */
    inertia.WithLogger(), // default logger
    // inertia.WithLogger(somelogger.New()),
)
Set custom container id
i, err := inertia.New(
    /* ... */
    inertia.WithContainerID("inertia"),
)
Set flash provider

Unfortunately (or fortunately) we do not have the advantages of such a framework as Laravel in terms of session management. In this regard, we have to do some things manually that are done automatically in frameworks.

One of them is displaying validation errors after redirects. You have to write your own implementation of gonertia.FlashProvider which will have to store error data into the user's session and return this data (you can get the session ID from the context depending on your application).

i, err := inertia.New(
    /* ... */
    inertia.WithFlashProvider(flashProvider),
)

Simple inmemory implementation of flash provider:

type InmemFlashProvider struct {
    errors map[string]inertia.ValidationErrors
    clearHistory map[string]bool
}

func NewInmemFlashProvider() *InmemFlashProvider {
    return &InmemFlashProvider{errors: make(map[string]inertia.ValidationErrors)}
}

func (p *InmemFlashProvider) FlashErrors(ctx context.Context, errors ValidationErrors) error {
    sessionID := getSessionIDFromContext(ctx)
    p.errors[sessionID] = errors
    return nil
}

func (p *InmemFlashProvider) GetErrors(ctx context.Context) (ValidationErrors, error) {
    sessionID := getSessionIDFromContext(ctx)
    errors := p.errors[sessionID]
    delete(p.errors, sessionID)
    return errors, nil
}

func (p *InmemFlashProvider) FlashClearHistory(ctx context.Context) error {
    sessionID := getSessionIDFromContext(ctx)
    p.clearHistory[sessionID] = true
    return nil
}

func (p *InmemFlashProvider) ShouldClearHistory(ctx context.Context) (bool, error) {
    sessionID := getSessionIDFromContext(ctx)
    clearHistory := p.clearHistory[sessionID]
    delete(p.clearHistory, sessionID)
    return clearHistory
}
History encryption (learn more)

Encrypt history:

// Global encryption:
i, err := inertia.New(
    /* ... */
    inertia.WithEncryptHistory(),
)

// Pre-request encryption:
ctx := inertia.SetEncryptHistory(r.Context())

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).

Clear history:

ctx := inertia.ClearHistory(r.Context())

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
Testing

Of course, this package provides convenient interfaces for testing!

func TestHomepage(t *testing.T) {
    body := ... // get an HTML or JSON using httptest package or real HTTP request.

    // ...

    assertable := inertia.AssertFromReader(t, body) // from io.Reader body
    // OR
    assertable := inertia.AssertFromBytes(t, body) // from []byte body
    // OR
    assertable := inertia.AssertFromString(t, body) // from string body

    // now you can do assertions using assertable.Assert[...] methods:
    assertable.AssertComponent("Foo/Bar")
    assertable.AssertVersion("foo bar")
    assertable.AssertURL("https://example.com")
    assertable.AssertProps(inertia.Props{"foo": "bar"})
    assertable.AssertEncryptHistory(true)
    assertable.AssertClearHistory(true)
    assertable.AssertDeferredProps(map[string][]string{"default": []string{"foo bar"}})
    assertable.AssertMergeProps([]string{"foo"})

    // or work with the data yourself:
    assertable.Component // Foo/Bar
    assertable.Version // foo bar
    assertable.URL // https://example.com
    assertable.Props // inertia.Props{"foo": "bar"}
    assertable.EncryptHistory // true
    assertable.ClearHistory // false
    assertable.MergeProps // []string{"foo"}
    assertable.Body // full response body
}

More community adapters

Also, you can check one more golang adapter called petaki/inertia-go.

Full list of community adapters is located on inertiajs.com.

Credits

This package is based on inertiajs/inertia-laravel and uses some ideas of petaki/inertia-go.

License

Gonertia is released under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddValidationErrors

func AddValidationErrors(ctx context.Context, errors ValidationErrors) context.Context

AddValidationErrors appends validation errors to the passed context.

func ClearHistory

func ClearHistory(ctx context.Context) context.Context

ClearHistory cleaning history state.

func ClearHistoryFromContext

func ClearHistoryFromContext(ctx context.Context) bool

ClearHistoryFromContext returns clear history value from the context.

func EncryptHistoryFromContext

func EncryptHistoryFromContext(ctx context.Context) (bool, bool)

EncryptHistoryFromContext returns history encryption value from the context.

func IsInertiaRequest

func IsInertiaRequest(r *http.Request) bool

IsInertiaRequest returns true if the request is an Inertia request.

func SetEncryptHistory

func SetEncryptHistory(ctx context.Context, encrypt ...bool) context.Context

SetEncryptHistory enables or disables history encryption.

func SetProp

func SetProp(ctx context.Context, key string, val any) context.Context

SetProp sets prop value to the passed context.

func SetProps

func SetProps(ctx context.Context, props Props) context.Context

SetProps sets props values to the passed context.

func SetTemplateData

func SetTemplateData(ctx context.Context, templateData TemplateData) context.Context

SetTemplateData sets template data to the passed context.

func SetTemplateDatum

func SetTemplateDatum(ctx context.Context, key string, val any) context.Context

SetTemplateDatum sets single template data item to the passed context.

func SetValidationError

func SetValidationError(ctx context.Context, key string, msg string) context.Context

SetValidationError sets validation error to the passed context.

func SetValidationErrors

func SetValidationErrors(ctx context.Context, errors ValidationErrors) context.Context

SetValidationErrors sets validation errors to the passed context.

Types

type AlwaysProp

type AlwaysProp struct {
	Value any
}

AlwaysProp is a property that will always evaluated.

https://inertiajs.com/partial-reloads

func Always

func Always(value any) AlwaysProp

func (AlwaysProp) Prop

func (p AlwaysProp) Prop() any

type AssertableInertia

type AssertableInertia struct {
	Body *bytes.Buffer
	// contains filtered or unexported fields
}

AssertableInertia is an Inertia response struct with assert methods.

func AssertFromBytes

func AssertFromBytes(t t, body []byte) AssertableInertia

AssertFromBytes creates AssertableInertia from the bytes body.

func AssertFromReader

func AssertFromReader(t t, body io.Reader) AssertableInertia

AssertFromReader creates AssertableInertia from the io.Reader body.

func AssertFromString

func AssertFromString(t t, body string) AssertableInertia

AssertFromString creates AssertableInertia from the string body.

func (AssertableInertia) AssertClearHistory

func (i AssertableInertia) AssertClearHistory(want bool)

AssertClearHistory verifies that clear history value from Inertia response and the passed value are the same.

func (AssertableInertia) AssertComponent

func (i AssertableInertia) AssertComponent(want string)

AssertComponent verifies that component from Inertia response and the passed component are the same.

func (AssertableInertia) AssertDeferredProps

func (i AssertableInertia) AssertDeferredProps(want map[string][]string)

AssertDeferredProps verifies that deferred props from Inertia response and the passed deferred props are the same.

func (AssertableInertia) AssertEncryptHistory

func (i AssertableInertia) AssertEncryptHistory(want bool)

AssertEncryptHistory verifies that encrypt history value from Inertia response and the passed value are the same.

func (AssertableInertia) AssertMergeProps

func (i AssertableInertia) AssertMergeProps(want []string)

AssertMergeProps verifies that merge props from Inertia response and the passed merge props are the same.

func (AssertableInertia) AssertProps

func (i AssertableInertia) AssertProps(want Props)

AssertProps verifies that props from Inertia response and the passed props are the same.

func (AssertableInertia) AssertURL

func (i AssertableInertia) AssertURL(want string)

AssertURL verifies that url from Inertia response and the passed url are the same.

func (AssertableInertia) AssertVersion

func (i AssertableInertia) AssertVersion(want string)

AssertVersion verifies that version from Inertia response and the passed version are the same.

type DeferProp

type DeferProp struct {
	Value any
	Group string
	// contains filtered or unexported fields
}

DeferProp is a property that will evaluate after page load.

https://v2.inertiajs.com/deferred-props

func Defer

func Defer(value any, group ...string) DeferProp

func (DeferProp) Merge

func (p DeferProp) Merge() DeferProp

func (DeferProp) Prop

func (p DeferProp) Prop() any

type FlashProvider

type FlashProvider interface {
	FlashErrors(ctx context.Context, errors ValidationErrors) error
	GetErrors(ctx context.Context) (ValidationErrors, error)
	ShouldClearHistory(ctx context.Context) (bool, error)
	FlashClearHistory(ctx context.Context) error
}

FlashProvider defines an interface for a flash data provider.

type Inertia

type Inertia struct {
	// contains filtered or unexported fields
}

Inertia is a main Gonertia structure, which contains all the logic for being an Inertia adapter.

func New

func New(rootTemplateHTML string, opts ...Option) (*Inertia, error)

New initializes and returns Inertia.

func NewFromBytes

func NewFromBytes(rootTemplateBs []byte, opts ...Option) (*Inertia, error)

NewFromBytes receive bytes with root template html and then initializes Inertia.

func NewFromFile

func NewFromFile(rootTemplatePath string, opts ...Option) (*Inertia, error)

NewFromFile reads all bytes from the root template file and then initializes Inertia.

func NewFromFileFS

func NewFromFileFS(rootFS fs.FS, rootTemplatePath string, opts ...Option) (*Inertia, error)

NewFromFileFS reads all bytes from the root template file and then initializes Inertia.

func NewFromReader

func NewFromReader(rootTemplateReader io.Reader, opts ...Option) (*Inertia, error)

NewFromReader reads all bytes from the reader with root template html and then initializes Inertia.

func NewFromTemplate

func NewFromTemplate(rootTemplate *template.Template, opts ...Option) (*Inertia, error)

NewFromTemplate receives a *template.Template and then initializes Inertia.

func (*Inertia) Back

func (i *Inertia) Back(w http.ResponseWriter, r *http.Request, status ...int)

Back creates plain redirect response to the previous url.

func (*Inertia) Location

func (i *Inertia) Location(w http.ResponseWriter, r *http.Request, url string, status ...int)

Location creates redirect response.

If request was made by Inertia - sets status to 409 and url will be in "X-Inertia-Location" header. Otherwise, it will do an HTTP redirect with specified status (default is 302 for GET, 303 for POST/PUT/PATCH).

func (*Inertia) Middleware

func (i *Inertia) Middleware(next http.Handler) http.Handler

Middleware returns Inertia middleware handler.

All of your handlers that can be handled by the Inertia should be under this middleware.

func (*Inertia) Redirect

func (i *Inertia) Redirect(w http.ResponseWriter, r *http.Request, url string, status ...int)

Redirect creates plain redirect response.

func (*Inertia) Render

func (i *Inertia) Render(w http.ResponseWriter, r *http.Request, component string, props ...Props) (err error)

Render returns response with Inertia data.

If request was made by Inertia - it will return data in JSON format. Otherwise, it will return HTML with root template.

If SSR is enabled, pre-renders JavaScript and return HTML (https://inertiajs.com/server-side-rendering).

func (*Inertia) ShareProp

func (i *Inertia) ShareProp(key string, val any)

ShareProp adds passed prop to shared props.

func (*Inertia) ShareTemplateData

func (i *Inertia) ShareTemplateData(key string, val any)

ShareTemplateData adds passed data to shared template data.

func (*Inertia) ShareTemplateFunc

func (i *Inertia) ShareTemplateFunc(key string, val any) error

ShareTemplateFunc adds the passed value to the shared template func map. If no root template HTML string has been defined, it returns an error.

func (*Inertia) SharedProp

func (i *Inertia) SharedProp(key string) (any, bool)

SharedProp return the shared prop.

func (*Inertia) SharedProps

func (i *Inertia) SharedProps() Props

SharedProps returns shared props.

type JSONMarshaller

type JSONMarshaller interface {
	Marshal(v any) ([]byte, error)
	Decode(r io.Reader, v any) error
}

JSONMarshaller is marshaller which use for marshal/unmarshal JSON.

type LazyProp deprecated

type LazyProp = OptionalProp

Deprecated: use OptionalProp.

func Lazy deprecated

func Lazy(value any) LazyProp

Deprecated: use Optional.

type Logger

type Logger interface {
	Printf(format string, v ...any)
	Println(v ...any)
}

Logger defines an interface for debug messages.

type MergeProps

type MergeProps struct {
	Value any
	// contains filtered or unexported fields
}

MergeProps is a property, which items will be merged instead of overwrite.

https://v2.inertiajs.com/merging-props

func Merge

func Merge(value any) MergeProps

func (MergeProps) Merge

func (p MergeProps) Merge() MergeProps

func (MergeProps) Prop

func (p MergeProps) Prop() any

type Option

type Option func(i *Inertia) error

Option is an option parameter that modifies Inertia.

func WithContainerID

func WithContainerID(id string) Option

WithContainerID returns Option that will set Inertia's container id.

func WithEncryptHistory

func WithEncryptHistory(encryptHistory ...bool) Option

WithEncryptHistory returns Option that will enable Inertia's global history encryption.

func WithFlashProvider

func WithFlashProvider(flash FlashProvider) Option

WithFlashProvider returns Option that will set Inertia's flash data provider.

func WithJSONMarshaller

func WithJSONMarshaller(jsonMarshaller JSONMarshaller) Option

WithJSONMarshaller returns Option that will set Inertia's JSON marshaller.

func WithLogger

func WithLogger(logs ...Logger) Option

WithLogger returns Option that will set Inertia's logger.

func WithSSR

func WithSSR(url ...string) Option

WithSSR returns Option that will enable server side rendering on Inertia.

func WithVersion

func WithVersion(version string) Option

WithVersion returns Option that will set Inertia's version.

func WithVersionFromFile

func WithVersionFromFile(path string) Option

WithVersionFromFile returns Option that will set Inertia's version based on file checksum.

func WithVersionFromFileFS

func WithVersionFromFileFS(rootFS fs.FS, path string) Option

WithVersionFromFileFS returns Option that will set Inertia's version based on file checksum from rootFS.

type OptionalProp

type OptionalProp struct {
	Value any
	// contains filtered or unexported fields
}

OptionalProp is a property that will evaluate when needed.

https://inertiajs.com/partial-reloads

func Optional

func Optional(value any) OptionalProp

func (OptionalProp) Prop

func (p OptionalProp) Prop() any

type Proper

type Proper interface {
	Prop() any
}

Proper is an interface for custom type, which provides property, that will be resolved.

type Props

type Props map[string]any

Props are the data that will be transferred and will be available in the front-end component.

func PropsFromContext

func PropsFromContext(ctx context.Context) Props

PropsFromContext returns props from the context.

type TemplateData

type TemplateData map[string]any

TemplateData are data that will be available in the root template.

func TemplateDataFromContext

func TemplateDataFromContext(ctx context.Context) TemplateData

TemplateDataFromContext returns template data from the context.

type TemplateFuncs

type TemplateFuncs map[string]any

TemplateFuncs are functions that will be available in the root template.

type TryProper

type TryProper interface {
	TryProp() (any, error)
}

TryProper is an interface for custom type, which provides property and error, that will be resolved.

type ValidationErrors

type ValidationErrors map[string]any

ValidationErrors are messages, that will be stored in the "errors" prop.

func ValidationErrorsFromContext

func ValidationErrorsFromContext(ctx context.Context) ValidationErrors

ValidationErrorsFromContext returns validation errors from the context.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL