Vektor - Build production-grade web services quickly

logo_transparent

Intro

Vektor enables development of modern web services in Go. Vektor is designed to simplify the development of web APIs by eliminating boilerplate, using secure defaults, providing plug-in points, and offering common pieces needed for web apps. Vektor is fairly opinionated, but aims to provide flexibility in the right places.

Background

We see Go as the best language to build web APIs and rich backend services, and so Vektor's Go components are all focused on building those things.

Vektor consists of components that can be used to help you build your web apps and services. Vektor components can be used alone or together. Below is a list of in-development and planned components.

In development:

Vektor API (beta)

The vk component is central to Vektor. It helps to quickly build production-ready API services with Go. It includes secure-by-default settings such as built-in LetsEncrypt, lots of customizability, and helpers galore. It will soon integrate with Suborbital's Hive job scheduler to allow performing more complex and performance-oriented work. vk enables minimal-boilerplate servers with an intuitive wrapper around the most performant HTTP router, httprouter.

Vektor Logger (beta)

vlog is a low-effort logging package that will allow for structured or text-based logging, that will easily work with popular third-party logging systems.

Planned:

Vektor Authentication

The vauth component will provide an authentication library for service-service authentication (such as between vk services) as well as client-server authentication that can be extended to fit any need including end-user authentication.

Getting started

Creating a vk server is extremely simple:

import "github.com/suborbital/vektor/vk"

server := vk.New(
	vk.UseAppName("Vektor API Server"),
	vk.UseDomain("vektor.example.com"),
)

This will configure a server that sets up a LetsEncrypt certificate for vektor.example.com by serving content on :443 and the ACME challenge server on :80. Other options are available, see the full documentation for details.

To serve something, you'll need a handler:

type PingResponse struct {
	Ping string `json:"ping"`
}

func HandlePing(r *http.Request, ctx *vk.Ctx) (interface{}, error) {
	ctx.Log.Info("ping!")

	return PingResponse{Ping: "pong"}, nil
}

As you can see, handler functions don't actually concern themselves with responding to a request, rather just returning some data. vk is designed to handle the specifics of the HTTP response for you, all you need to do is return (interface{}, error). Vektor handles the returned data based on a simple set of rules. The simplest form is this: Want to respond with JSON? Just return a struct. To control exactly how the response behaves, check out the Response and Error types.

Mounting handlers to the server is just as easy:

server.GET("/ping", HandlePing)

And finally, start your server:

if err := server.Start(); err != nil {
	log.Fatal(err)
}

That's just the beginning! Vektor includes powerful features like composable middleware, route groups, some handy built-in helpers, and more.

To learn about everything Vektor can do, visit the guide

Copyright Suborbital contributors 2020

Owner
Suborbital
Rocket-fueled open source platform tools
Suborbital
Comments
  • feat(router): Rework middlewares so they work as an onion

    feat(router): Rework middlewares so they work as an onion

    Closes #79

    • replaces the Before and After middlewares with a single Middleware type that takes a vk.HandlerFunc and returns a vk.HandlerFunc
    • Reworks existing middlewares into this pattern
    • Adds a test to make sure that the functionality works both on individual handlers, and on the global server as well
    • Adds tests that show how to order the middlewares and what the execution order is like.

    Reworked the vektor middlewares. Previously vektor's middlewares were "beads on a string". This change turns them into "layers of an onion". I expect a bunch of our services might need some changing, but generally this will enable us to do proper logging / error handling / panic recovery / metrics / tracing because we can start a trace and immediately defer stop them because the actual handler is encapsulated.

    In the following case the middlewares are going to be ran in the following order:

    g := vk.Group("/b").Middleware(
    	midWare1,
    	midWare2,
    	midWareAfter1,
    	midWareAfter2,
    )
    g.GET("/central", vk.WrapMiddleware([]vk.Middleware{
    	handlerMid1,
    	handlerMid2,
    	handlerAfter1,
    	handlerAfter2,
    ), actualHandler)
    
    Request
        |
        v
    G() midWare1
        |
        v
    G() midWare2
        |
        v
    handlerMid1
        |
        v
    handlerMid2
        |
        v
    (((--- Actual Handler ---)))
        |
        v
    handlerAfter2
        |
        v
    handlerAfter1
        |
        v
    G() midWareAfter2
        |
        v
    G() midWareAfter1
        |
        v
    Response
    

    The later a middleware it is in the []vk.Middleware{} list, the closer it is to the handler.

    Before type middleware

    The middlewares usually look like this (putting the wrapped handler on the tail):

    middleware(responsewriter, request) {
        // do stuff in middleware
       
       return wrappedHandler(responsewriter, request)
    }
    

    After type mw

    The after type middlewares look like this, putting the wrapped handler on the leading edge:

    middleware(responsewriter, request) {
        res, err := wrappedHandler(responsewriter, request)
    
        // do stuff in the middleware
    }
    

    Surround type mw

    For logging, tracing, metrics, panic recovery middlewares, they will look like this:

    middleware(responsewriter, request) {
        // do stuff here like start a span, start a logging context, set attributes on metrics, whatever
    
        res, err := wrappedHandler(responsewriter, request) // do the actual work here
    
        // do more stuff here, like finish span, check for errors, check for panics, whatever
    }
    
  • fix: Remove leading underscore

    fix: Remove leading underscore

    go-envconfig made a change in v0.8.x where any leading underscore (ex: _APP_NAME) is invalid and will break env var parsing.

    This commit removes the leading prefixed underscore from built-in options, and sets the default prefix string to VK_.

    All new prefixes must end with an underscore when this change is released and adopted. For example, an app that used _MY_ENV for an env var and PREFIX for the prefix, but be changed to PREFIX_ for the prefix and MY_ENV for the env var.

  • chore(deps): update golang.org/x/crypto digest to eccd636

    chore(deps): update golang.org/x/crypto digest to eccd636

    Mend Renovate

    This PR contains the following updates:

    | Package | Type | Update | Change | |---|---|---|---| | golang.org/x/crypto | require | digest | 4ba4fb4 -> eccd636 |


    Configuration

    📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

    🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

    Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

    🔕 Ignore: Close this PR and you won't be reminded about this update again.


    • [ ] If you want to rebase/retry this PR, click this checkbox.

    This PR has been generated by Mend Renovate. View repository job log here.

  • Update module go to 1.19

    Update module go to 1.19

    Mend Renovate

    This PR contains the following updates:

    | Package | Type | Update | Change | |---|---|---|---| | go (source) | golang | minor | 1.18 -> 1.19 |


    Release Notes

    golang/go

    v1.19.1

    v1.19.0


    Configuration

    📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

    🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

    Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

    🔕 Ignore: Close this PR and you won't be reminded about this update again.


    • [ ] If you want to rebase/retry this PR, click this checkbox.

    This PR has been generated by Mend Renovate. View repository job log here.

  • Make middlewares wrap handlers rather than run before and after

    Make middlewares wrap handlers rather than run before and after

    Currently middlewares and afterwares are handled in the vk/middleware.go file augmentHandler method. The way this works is that it calls the middlewares first, one by one, and finishes with them, then calls the actual core handler function, and then calls a bunch of other afterwares, one by one.

    An architecture diagram would look like this:

    Request -> (mw1) -> (mw2) -> (core handler) -> (aw1) -> (aw2) -> Response
    

    Instead middlewares should be wrapping the other handlers. That way we can construct a layered structure that simplifies code and is easier to reason about, plus it would remove distinction between a middleware (used here to mean running code that should happen before the core handler does anything with it), and afterware (used here to mean running code that should happen after the core handler is done with the request).

    The target structure should look like this:

    Request ->                                                                                      -> Response
               ( mw1 ->                                                    -> mw1, but after code )
                        ( mw2 ->                  -> mw2, but after code )
                                 ( core handler )
    
  • chore: env var rename for prefix

    chore: env var rename for prefix

    Given the following env var values:

    NAME="hello"
    FOO_VERSION="34"
    FOO_TYPE="underscored"
    

    The following does NOT work:

    type Wrapper struct {
    	Name        string      `env:"NAME"`
    	Subordinate Subordinate `env:",prefix=FOO"`
    }
    
    type Subordinate struct {
    	Version int    `env:"_VERSION"`
    	Type    string `env:"_TYPE"`
    }
    

    But this one WILL work:

    type Wrapper struct {
    	Name        string      `env:"NAME"`
    	Subordinate Subordinate `env:",prefix=FOO_"`
    }
    
    type Subordinate struct {
    	Version int    `env:"VERSION"`
    	Type    string `env:"TYPE"`
    }
    

    The reason is that the individual field names all need to adhere to the env var field name format (only letters (whatever case), numbers, underscore, starts with letter)*, but if we put the underscore in the suborbdinate's field's names at the beginning, that fails.

    The prefix has no such limitation, so the easy fix is to move the leading underscore from the subordinate struct to be a trailing underscore of the prefix.

    *the spec technically says it should be only uppercase letters, however the application can tolerate other chars and must distinguish between uppercase and lowercase letters:

    Environment variable names used by the utilities in the Shell and Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase letters, digits, and the '_' (underscore) from the characters defined in Portable Character Set and do not begin with a digit. Other characters may be permitted by an implementation; applications shall tolerate the presence of such names. Uppercase and lowercase letters shall retain their unique identities and shall not be folded together. The name space of environment variable names containing lowercase letters is reserved for applications. Applications can define any environment variables with names from this name space without modifying the behavior of the standard utilities.

  • Update golang.org/x/crypto digest to 4ba4fb4

    Update golang.org/x/crypto digest to 4ba4fb4

    Mend Renovate

    This PR contains the following updates:

    | Package | Type | Update | Change | |---|---|---|---| | golang.org/x/crypto | require | digest | 0559593 -> 4ba4fb4 |


    Configuration

    📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

    🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

    Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

    🔕 Ignore: Close this PR and you won't be reminded about this update again.


    • [ ] If you want to rebase/retry this PR, click this checkbox.

    This PR has been generated by Mend Renovate. View repository job log here.

  • Update module github.com/sethvargo/go-envconfig to v0.8.2

    Update module github.com/sethvargo/go-envconfig to v0.8.2

    Mend Renovate

    This PR contains the following updates:

    | Package | Type | Update | Change | |---|---|---|---| | github.com/sethvargo/go-envconfig | require | minor | v0.7.0 -> v0.8.2 |


    Release Notes

    sethvargo/go-envconfig

    v0.8.2

    Compare Source

    What's Changed

    Full Changelog: https://github.com/sethvargo/go-envconfig/compare/v0.8.1...v0.8.2

    v0.8.1

    Compare Source

    What's Changed

    Full Changelog: https://github.com/sethvargo/go-envconfig/compare/v0.8.0...v0.8.1

    v0.8.0

    Compare Source

    What's Changed

    The previous implementation of overwrite would always overwrite values in the given struct, even if values existed. While this is the definition of overwrite, it unintentionally extended to default values as well. So even if a value was explicitly set on a struct, it would be overwritten with the "default" value set in envconfig. This was an unexpected behavior, since defaults should take the lowest precedence.

    The new implementation has the following behavior with overwrite:

    • If the struct field has the zero value and a default is set:

      • If no environment variable is specified, the struct field will be populated with the default value.

      • If an environment variable is specified, the struct field will be populate with the environment variable value.

    • If the struct field has a non-zero value and a default is set:

      • If no environment variable is specified, the struct field's existing value will be used (the default is ignored).

      • If an environment variable is specified, the struct field's existing value will be overwritten with the environment variable value.

    As part of this change, decoder interfaces are only processed when an environment (or a default) is present.

    Full Changelog: https://github.com/sethvargo/go-envconfig/compare/v0.7.0...v0.8.0


    Configuration

    📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

    🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

    Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

    🔕 Ignore: Close this PR and you won't be reminded about this update again.


    • [ ] If you want to rebase/retry this PR, click this checkbox.

    This PR has been generated by Mend Renovate. View repository job log here.

  • feat(vektor): Rework http server structure to allow middleware wrapping

    feat(vektor): Rework http server structure to allow middleware wrapping

    Closes #79

    This is the version that touches the least amount of code (yes, even with this amount of changes) in a way that still achieves our need to wrap middlewares, while also not painting ourselves in a corner.

    New things we can now do

    • each group can have their own group level middlewares which would automatically apply to all routes in that group
    • each route can have their own middlewares individually. These individual middlewares will wrap the core handler first, and THEN the group level middlewares wrap these
    • bear in mind that if we're adding a subgroup with middlewares to another group, both group's middlewares are going to wrap the core handler:
      g1 := group.WithMiddlewares(subgroup MW)
      g1.GET("/somepath", handler, handlerMW)
      
      g2 := group.WithMiddlewares(outgroup MW)
      g2.AddGroup(g1)
      
      // request -> outGroup MW -> subGroup MW -> handler MW -> handler
      

    Important changes in different places

    HTTP handler signature

    It changed from func(request, ctx) (interface{}, error) to func(writer, request, ctx) error.

    The main reason for this were two-fold

    1. writer needed to be included, so websocket could be upgraded (websocket is just a spicy GET request)
    2. return needs to be an error only (to be picked up by an error handling middleware, more on this later), which means the response, upon a successful handling of whatever, should be sent from the core handler. We need this because previously we returned an interface, and then a wrapping function did the actual response. There was an http and a websocket version of this, but because I'm making websocket just another http handler, and the http response function was mangling / overriding response codes and whatnot, the websocket 101 upgrade never made it back to the client.

    The respond functions

    There are a bunch: RespondJSON, RespondString, RespondBytes. They should be called from within the core handler once you're ready to send back data to the client. They has the flexibility that we can choose not to send back anything in case we want to write directly to the responsewriter, such as in the case of a websocket handler.

    It also has two extra types: RawString and RawBytes. These exist because the response will json encode things, so if you pass in a naked string, like hello, it will be returned as a json string, which means wrapped in double quotes.

    If you send the data into the respond function as a rawstring / rawbytes, it will NOT be json encoded, so the text won't be wrapped in quotes.

    The reasoning for that is because I did not adjust any of the tests assertions, and some of them were expecting an unquoted string.

    The error middleware

    Every outer group should have the error middleware attached to it. What it does is calls the inner handler (core handler + whatever other middleware we wrapped it in (panic should be immediately wrapping it), and if that returns an error, it checks whether it's a trusted error (I repurposed vk.E for this), I return the vk.E json encoded with the status code in it.

    Otherwise I don't trust them, so they get a generic 500 internal server error.

    And then return nil, because we handled the error.

    Websocket

    So websockets are just GET requests that get upgraded, and a connection made, and then the connection receives messages and sends messages. Which also means we can use the http handlers here, so an entire branch of 🕸️ 🧦 code is just gone.

    There's a convenience wrapper around websocket handlers that takes care of the upgrading and whatnot, but you can write a plain old http handler and upgrade the connection manually if you so wish. It's flexible! And passes the test.

    Out of scope for this PR

    ctx

    I left ctx as is. Technically there are two problems with it:

    1. It should be the very first argument because that's a pattern in Go-the-language
    2. We should be using the standard context.Context rather than vk.Ctx

    logs

    No change to logging has been done. Currently vk.Ctx has log in it. Once vk.Ctx is gone, logger will be passed into the different middlewares.

    tracing middleware

    Also out of scope, because this PR is the base work that enables new bits to be attached to everything.

    removing custom groups

    http tree mux has groups inside of it by default: https://github.com/dimfeld/httptreemux#routing-groups

    We don't need to reinvent the wheel.

  • Update golang.org/x/crypto digest to 0559593

    Update golang.org/x/crypto digest to 0559593

    Mend Renovate

    This PR contains the following updates:

    | Package | Type | Update | Change | |---|---|---|---| | golang.org/x/crypto | require | digest | 793ad66 -> 0559593 |


    Configuration

    📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

    🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

    Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

    🔕 Ignore: Close this PR and you won't be reminded about this update again.


    • [ ] If you want to rebase/retry this PR, click this checkbox.

    This PR has been generated by Mend Renovate. View repository job log here.

  • Update module github.com/stretchr/testify to v1.8.0

    Update module github.com/stretchr/testify to v1.8.0

    Mend Renovate

    This PR contains the following updates:

    | Package | Type | Update | Change | |---|---|---|---| | github.com/stretchr/testify | require | minor | v1.7.1 -> v1.8.0 |


    Release Notes

    stretchr/testify

    v1.8.0

    Compare Source

    v1.7.5

    Compare Source

    v1.7.4

    Compare Source

    v1.7.3

    Compare Source

    v1.7.2

    Compare Source


    Configuration

    📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

    🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

    Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

    🔕 Ignore: Close this PR and you won't be reminded about this update again.


    • [ ] If you want to rebase/retry this PR, click this checkbox.

    This PR has been generated by Mend Renovate. View repository job log here.

  • Accept connections with or without trailing slash

    Accept connections with or without trailing slash

    Whether or not a request can be handled with a trailing slash is up to the user, but Vektor should handle both.

    For example, it's common to do something like:

    build := vk.Group("/build")
    build.GET("/", ...)
    

    In this case, requests must be made to /build/ rather than /build. Vektor should accept both.

  • Allow binding to port 0

    Allow binding to port 0

    It's helpful to have the option to defer port umber selection to the OS when writing tests to avoid bind conflicts. It would be great if this were an option in Vektor.

    summary of changes:

    1. Differentiate between HTTP Port not set and HTTP Port set to 0
    2. Expose function to get allocated port
  • Add benchmarks for Vektor versus other Go web frameworks

    Add benchmarks for Vektor versus other Go web frameworks

    While Vektor is not directly comparable to every other Go web framework due to its different feature set, it would still be useful to add benchmarks.

    See: https://github.com/smallnest/go-web-framework-benchmark

  • OpenAPI support for Vektor

    OpenAPI support for Vektor

    It would be neat if Vektor could generate a list of registered routes that could be compared against an existing OpenAPI spec to ensure that all of the routes are covered and match. Later it might also be possible to extract type metadata (perhaps via reflection?) into Vektor for autogenerating entire OpenAPI specs.

A REST framework for quickly writing resource based services in Golang.

What is Resoursea? A high productivity web framework for quickly writing resource based services fully implementing the REST architectural style. This

Sep 27, 2022
GoAdmin Instruction - A golang framework help gopher quickly build a data visualization platform

GoAdmin Instruction - A golang framework help gopher quickly build a data visualization platform

Jan 21, 2022
Flamingo Framework and Core Library. Flamingo is a go based framework for pluggable web projects. It is used to build scalable and maintainable (web)applications.
Flamingo Framework and Core Library. Flamingo is a go based framework for pluggable web projects. It is used to build scalable and maintainable (web)applications.

Flamingo Framework Flamingo is a web framework based on Go. It is designed to build pluggable and maintainable web projects. It is production ready, f

Jan 5, 2023
Flexible E-Commerce Framework on top of Flamingo. Used to build E-Commerce "Portals" and connect it with the help of individual Adapters to other services.

Flamingo Commerce With "Flamingo Commerce" you get your toolkit for building fast and flexible commerce experience applications. A demoshop using the

Dec 31, 2022
Bramble is a production-ready GraphQL federation gateway.
Bramble is a production-ready GraphQL federation gateway.

Bramble is a production-ready GraphQL federation gateway. It is built to be a simple, reliable and scalable way to aggregate GraphQL services together.

Jan 3, 2023
A Go framework for building JSON web services inspired by Dropwizard

Tiger Tonic A Go framework for building JSON web services inspired by Dropwizard. If HTML is your game, this will hurt a little. Like the Go language

Dec 9, 2022
package for building REST-style Web Services using Go

go-restful package for building REST-style Web Services using Google Go Code examples using v3 REST asks developers to use HTTP methods explicitly and

Jan 1, 2023
A minimal framework to build web apps; with handler chaining, middleware support; and most of all standard library compliant HTTP handlers(i.e. http.HandlerFunc).
A minimal framework to build web apps; with handler chaining, middleware support; and most of all standard library compliant HTTP handlers(i.e. http.HandlerFunc).

WebGo v4.1.3 WebGo is a minimalistic framework for Go to build web applications (server side) with zero 3rd party dependencies. Unlike full-fledged fr

Jan 1, 2023
Go-app is a package to build progressive web apps with Go programming language and WebAssembly.
Go-app is a package to build progressive web apps with Go programming language and WebAssembly.

Go-app is a package to build progressive web apps with Go programming language and WebAssembly.

Dec 30, 2022
based on go lang build WEB development framework for go lang beginners .

based on go lang build WEB development framework for go lang beginners .

Oct 31, 2021
🚀‏‏‎ ‎‏‏‎‏‏‎‎‎‎‎‎Copper is a Go toolkit complete with everything you need to build web apps.

Copper Copper is a Go toolkit complete with everything you need to build web apps. It focuses on developer productivity and makes building web apps in

Jan 7, 2023
⚡ Rux is an simple and fast web framework. support middleware, compatible http.Handler interface. 简单且快速的 Go web 框架,支持中间件,兼容 http.Handler 接口

Rux Simple and fast web framework for build golang HTTP applications. NOTICE: v1.3.x is not fully compatible with v1.2.x version Fast route match, sup

Dec 8, 2022
Golanger Web Framework is a lightweight framework for writing web applications in Go.

/* Copyright 2013 Golanger.com. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except

Nov 14, 2022
re:Web enables classic web applications to run on AWS Lambda.
re:Web enables classic web applications to run on AWS Lambda.

re:Web re:Web enables classic web applications to run on AWS Lambda. re:Web interfaces with the Lambda Runtime API. It translates API Gateway requests

Jan 1, 2023
Roche is a Code Generator and Web Framework, makes web development super concise with Go, CleanArch
Roche is a Code Generator and Web Framework, makes web development super concise with Go, CleanArch

It is still under development, so please do not use it. We plan to release v.1.0.0 in the summer. roche is a web framework optimized for microservice

Sep 19, 2022
A powerful go web framework for highly scalable and resource efficient web application

webfr A powerful go web framework for highly scalable and resource efficient web application Installation: go get -u github.com/krishpranav/webfr Exa

Nov 28, 2021
A powerful go web framework for highly scalable and resource efficient web application

A powerful go web framework for highly scalable and resource efficient web application

Oct 3, 2022
Chainrand contract + web frontend + web backend

Chainrand-web This repo contains the implementation of Chainrand. https://chainrand.io Smart Contract Contains functionality to tie a Chainlink VRF to

Dec 8, 2021