Skip to content
forked from gebes/there

⚡️ Robust Web Framework to build Go Services

License

Notifications You must be signed in to change notification settings

ninjarogue/there

 
 

Repository files navigation

Gopher There

There is a robust and minimalistic Web Framework that helps you to build fabulous services with GoLang in no time!

Made with Go Go Version Documentation GoDoc GoReportCard License Latest releace Latest releace Latest releace



Maintained? Maintainer

🔥 Get Started

🔨 Create a Project

  1. Ensure you have Go installed.
  2. Create a project with go mod init github.com/user/repository
  3. Install There with the go get command
go get -u github.com/Gebes/there/v2
  1. Create a main.go file
package main

import . "github.com/Gebes/there/v2"

func main() {
	router := NewRouter() // Create a new router
	
	// Register GET route /
	router.Get("/", func(request HttpRequest) HttpResponse {
		return Json(StatusOK, Map{
			"message": "Hello World!",
		})
	})
	
	err := router.Listen(8080) // Start listening on 8080

	if err != nil {
		panic(err)
	}
}

📚 Check the Documentation for more info

🤔 Why There?

The general problem with many routers is the way you handle responses. Most frameworks make it too complex or do not offer the proper abstraction to get the same result in a short amount of time.
The goal of There is to give developers the right tool to create robust apis in no time.

We solve this problem by providing simple interfaces to control the flow of your API.
Got an error while fetching the user? Just return Error(status, err). Want to return some data? Just return Json(status, data). Is the data too large? Compress it return Gzip(Json(status, data)).
This type of control flow is way easier to read, and it doesn't take away any freedom!

Imports

If you create an API with There you do not need to import net/http even once! Simply import

import . "github.com/Gebes/there/v2"

and There provides you with all the handlers, constants and interfaces you need to create a router, middleware or anything else!
There provides enough constants for you! In total there are 140 of them.

  • Method (MethodGet, MethodPost)
  • Status (StatusOK, StatusInternalServerError)
  • RequestHeader/ResponseHeader (RequestHeaderContentType, RequestHeaderAcceptEncoding, ResponseHeaderLocation)
  • ContentType (ContentTypeApplicationJson, ContentTypeApplicationXml)

🧠 Philosophy

Focus on your project, not on the framework.

The goal of There is to save time and provide all the functionality a backend developer needs daily. Therefore, There should always keep it simple and only add complexity if there is no reasonable workaround.
New Go Developers often struggle with chores they are not used to, and There should help them make life easier.

So There should be something for everyone.

⭐️ Features

Straightforward routing

Routing with There is easy! Simply create a new router and add GET, POST or different handlers. Use groups to define multiple handlers simultaneously and protect your handlers with middlewares. Define route variables with : and you have all the things you need for routing.

	router := NewRouter()

	router.Group("/user").
		Get("/", Handler). // /user
		Post("/", Handler).
		Patch("/", Handler)

	router.Group("/user/post").
		Get("/:id", Handler).
		Post("/", Handler)

	router.
		Get("/details", Handler).IgnoreCase()

🧑‍💻 View more code examples

Minimalistic control flow

Controlling your route's flow with There is a delight! It is easy to understand and fast to write.
A HttpResponse is basically a http.handler. There provides several handlers out of the box!

func CreatePost(request HttpRequest) HttpResponse {
	var body Post
	err := request.Body.BindJson(&body) // Decode body
	if err != nil { // If body was not valid json, return bad request error
		return Error(StatusBadRequest, "Could not parse body: "+err.Error())
	}

	post := postById(body.Id)
	if post != nil { // if the post already exists, return conflict error
		return Error(StatusConflict, "Post with this ID already exists")
	}

	posts = append(posts, body) // create post
	return Json(StatusCreated, body) // return created post as json
}

This handler uses Error and Json. By returning a HttpResponse, the handler chain will break and There will render the given response.

📚 Documentation
🧑‍💻 View full code example

Expandable - add your own control flow

Simply create your own HttpResponse to save time. However, if you need some inspiration, look into the response.go file.

For example, let us create a Msgpack response. By default, there does not provide a Msgpack response, because this would require a third-party dependency. But it is not much work to create your own Msgpack HttpResponse:

import (
    . "github.com/Gebes/there/v2"
    "github.com/vmihailenco/msgpack/v5"
)

//Msgpack takes a StatusCode and data which gets marshaled to Msgpack
func Msgpack(code int, data interface{}) HttpResponse {
   msgpackData, err := msgpack.Marshal(data) // marshal the data
   if err != nil {
      panic(err) // panic if the data was invalid. can be caught by Recoverer
   }
   return WithHeaders(MapString{ // set proper content-type
      ResponseHeaderContentType: "application/x-msgpack",
   }, Bytes(code, msgpackData))
}

func Get(request HttpRequest) HttpResponse {
   return Msgpack(StatusOK, map[string]string{ // now use the created response
      "Hello": "World",
      "How":   "are you?",
   })
}

There provides enough lower-level HttpResponses to build another one on top of it. At the bottom, we have a "Bytes" response, which writes the given bytes and the status code.
Wrapped around the "Bytes" response, you can find a "WithHeaders" response, adding the ContentType header.

As you see, it is only a few lines of code to have a custom HttpResponse.

🧑‍💻 View full code example

Complete middleware support

To keep things simple, you can use already existing middlewares with little to no change, and you can use the simple control flow from There in your middlewares.

Here is an example:

func main() {
   router := NewRouter()

   // Register global middlewares
   router.Use(middlewares.Recoverer)
   router.Use(middlewares.Cors(middlewares.AllowAllConfiguration()))
   router.Use(GlobalMiddleware)

   router.
   	Get("/", Get).With(RouteSpecificMiddleware) // Register route with middleware

   err := router.Listen(8080)
   if err != nil {
      log.Fatalln("Could not listen to 8080", err)
   }
}

func GlobalMiddleware(request HttpRequest, next HttpResponse) HttpResponse {
   // Check the request content-type
   if request.Headers.GetDefault(RequestHeaderContentType, "") != ContentTypeApplicationJson {
      return Error(StatusUnsupportedMediaType, "Header " + RequestHeaderContentType + " is not " + ContentTypeApplicationJson)
   }
   
   return next // Everything is fine until here, continue
}

func RouteSpecificMiddleware(request HttpRequest, next HttpResponse) HttpResponse {
   return WithHeaders(MapString{
      ResponseHeaderContentLanguage: "en",
   }, next) // Set the content-language header by wrapping next with WithHeaders
}

With the .Use method, you can add a global middleware. No matter on which group you call it, it will be global.
On the other side, if you use the .With method you can only add a middleware to one handler! Not to a whole group.

The GlobalMiddleware in this code checks if the request has application/json as content-type. If not, the request will fail with an error. Compared to the GlobalMiddleware, the RouteSpecificMiddleware does not change the control flow but adds data to the response.

Be careful in this example. Global middlewares will always be called first, so if the global middleware returns an error, the content-language header won't be set by the RouteSpecificMiddleware middleware.

Using already existing middlewares

If you have other middlewares, which you created using other routers, then there is a high chance that you can use it in There without changing much.

As an example, let us have a look at the Recoverer middleware.

func Recoverer(request HttpRequest, next HttpResponse) HttpResponse {
   fn := func(w http.ResponseWriter, r *http.Request) {
      defer func() {
         if rvr := recover(); rvr != nil && rvr != http.ErrAbortHandler {
            Error(StatusInternalServerError, rvr).ServeHTTP(w, r)
         }
      }()
      next.ServeHTTP(w, r)
   }
   return HttpResponseFunc(fn)
}

It is a trivial Recoverer. The only things you need to change are the types and the parameters. There provides you all the types required, so that you don't need to import "net/http".

🧑‍💻 View full code example

Lightweight - no dependencies

There was built to be lightweight and robust, so if you use There, you do not need to worry about having many more external dependencies because There has none!

Robust - 99,6% coverage

Almost everything in There has a corresponding test. We tested the framework well in production and wrote enough test cases, so in the end almost everything that makes sense to test was tested.

About

⚡️ Robust Web Framework to build Go Services

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 99.9%
  • Other 0.1%