Welcome
Welcome, and thank you for your interest in torque
!
torque is a Golang powered backend-for-frontend and server-side rendering framework for building modern hypermedia driven applications.
Installation
go get github.com/tylermmorton/torque@latest
Quick Start
At its core torque
is just a router compatible with Go’s standard net/http
package. The router implements http.Handler
so you'll simply need to integrate it with your existing net/http
application.
Or, if you're starting from scratch, you can use the NewRouter
constructor to create a new router and pass it to http.ListenAndServe
:
package main
import (
"net/http"
"github.com/tylermmorton/torque"
)
func main() {
r := torque.NewRouter()
http.ListenAndServe("localhost:9001", r)
}
The NewRouter
constructor takes a variadic list of Route
arguments. You are meant to compose your torque application at startup this way:
package main
import (
"net/http"
"github.com/tylermmorton/torque"
)
func main() {
r := torque.NewRouter(
torque.WithRedirect("/", "/welcome", http.StatusTemporaryRedirect),
torque.WithRouteModule("/login", &LoginRouteModule{/* ... */}),
torque.WithRouteModule("/signup", &SignupRouteModule{/* ... */}),
torque.WithGroup(
torque.WithMiddleware(authMiddleware()),
torque.WithRouteModule("/dashboard", &DashboardRouteModule{/* ... */}),
),
)
http.ListenAndServe("localhost:9001", r)
}
The primary component for building your torque application is the RouteModule
, but the torque
framework offers a series of pre-built Route
components that you can leverage to build your app quickly:
Router Composition Functions | Description |
---|---|
WithHandler | Registers an http.Handler to the given route |
WithMiddleware | Registers an http.HandlerFunc to be used as middleware for all incoming requests. |
WithRedirect | Handles incoming requests at the given from route by redirecting them to the given to route and responding with the configured statusCode |
WithRouteModule | Registers a torque RouteModule to the given route |
WithEventStream | Push server-sent events over Go channels via text/event-stream |
WithFileServer | Serves the given dir via HTTP GET on the given route |
WithFileSystemServer | Serves the given fs.FS via HTTP GET on the given route |
WithNotFoundHandler | Handles all requests who fail with status code 404 |
WithMethodNotAllowedHandler | Handles all requests who fail with status code 405 |
Route Modules 101
Route Modules take advantage of Golang's implicit interface implementations feature to make it easier to build your application. It enables torque to handle the wiring and plumbing of the application and leave you to focus on adding value for your users.
In reality, a Route Module is a struct type that implements one of the interfaces in the Module API. Perhaps the most common interface to implement is torque.Renderer
:
package torque
type Renderer interface {
Render(wr http.ResponseWriter, req *http.Request, loaderData any) error
}
The following is an example LoginRouteModule
that implements the torque.Renderer
interface and renders a simple login form:
package main
import (
"net/http"
"github.com/tylermmorton/torque"
)
type LoginRouteModule struct{
// define any dependencies here
}
// it may be useful to assert implementations
var _ interface {
torque.Renderer
} = &LoginRouteModule{}
// Render satisfies the torque.Renderer interface
func (m *LoginRouteModule) Render(wr http.ResponseWriter, req *http.Request, loaderData any) error {
wr.Write([]byte(`
<html>
<body>
<h1>Login</h1>
<form method="POST">
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">Login</button>
</form>
</body>
</html>
`))
return nil
}
When LoginRouteModule
is added to the router, torque will perform type assertions against the different interfaces in the Module API to determine what types of requests can be handled.
In this case, the LoginRouteModule
implements the torque.Renderer
interface, so torque will register a handler for all incoming GET
requests with Content-Type
set to text/html
.
Another common interface is torque.Loader
, which can be used to load data during incoming HTTP GET requests.
package torque
type Loader interface {
Load(req *http.Request) (any, error)
}
The following is an example MarketRouteModule
that implements the torque.Loader
interface and loads data from a marketplace service:
package main
import (
"net/http"
"github.com/tylermmorton/torque"
)
type MarketRouteModule struct {
// dummy marketplace service
MarketSvc market.Service
}
// it may be useful to assert implementations
var _ interface {
torque.Loader
torque.Renderer
} = &MarketRouteModule{}
type SearchParams struct {
Query string `json:"q"`
MinPrice int `json:"min_price"`
MaxPrice int `json:"max_price"`
}
// Load satisfies the torque.Loader interface
func (m *MarketRouteModule) Load(req *http.Request) (any, error) {
params, err := torque.DecodeQuery[SearchParams](req)
if err != nil {
return nil, nil
}
res, err := m.MarketSvc.Search(req.Context(), params)
if err != nil {
return nil, err
}
return res, nil
}
You can return any
data from a Loader. By default, this data will be returned as the response to all incoming HTTP GET requests with Content-Type
set to application/json
.
However, if your module also implements Renderer
, the data will be passed to the Render
method as the loaderData
argument. This can, for example, be used when rendering pages from templates:
func (m *MarketRouteModule) Render(wr http.ResponseWriter, req *http.Request, loaderData any) error {
return template.Must(template.New("market").Parse(`
<html>
<body>
<h1>Marketplace</h1>
{{ range . }}
<div>
<h2>{{ .Name }}</h2>
<p>{{ .Description }}</p>
<p>{{ .Price }}</p>
</div>
{{ end }}
</body>
</html>
`)).Execute(wr, loaderData)
}
There's plenty more to learn about Route Modules, but this should be enough to get you started. For more documentation on Route Modules visit the dedicated Modules API page.