Create an HTTP server
One of the primary use cases for gosoline is to create a REST web service. In this tutorial, you'll do just that!
Specifically, you'll use gosoline to create a web service that handles a single endpoint:
GET /todo?id={ID}&text={TODO TEXT}
This endpoint will:
- Accept the data from the request's querystring
- Send a Json response with the
id
andtext
from the request, along with an ISO-formatted creation date and time.
Here's an example response:
{
"Id": 3,
"Text": "doit",
"CreatedAt": "2023-09-05T11:18:09.325852335+02:00"
}
Before you begin
Before you begin, make sure you have Golang installed on your machine.
Set up your file structure
First, you need to set up the following file structure:
todo/
├── handler.go
├── main.go
└── config.dist.yml
For example, in Unix, run:
mkdir todo; cd todo
touch handler.go
touch main.go
touch config.dist.yml
Those are all the files you need to build your web service with gosoline! Next, you'll implement each of these files, starting with handler.go
.
Implement handler.go
In handler.go
, add the following code:
handler.go
package main
import (
"context"
"time"
"github.com/justtrackio/gosoline/pkg/cfg"
"github.com/justtrackio/gosoline/pkg/httpserver"
"github.com/justtrackio/gosoline/pkg/log"
)
type Todo struct {
Id int `form:"id"`
Text string `form:"text"`
CreatedAt time.Time
}
type TodoHandler struct {
logger log.Logger
}
func (t TodoHandler) GetInput() interface{} {
return &Todo{}
}
func NewTodoHandler(ctx context.Context, config cfg.Config, logger log.Logger) (*TodoHandler, error) {
handler := &TodoHandler{
logger: logger,
}
return handler, nil
}
func (t TodoHandler) Handle(ctx context.Context, request *httpserver.Request) (*httpserver.Response, error) {
// Initialize a Todo struct from the request body
todo := request.Body.(*Todo)
// Set its CreatedAt to now
todo.CreatedAt = time.Now()
// Log the request using the TodoHandler struct's logger
t.logger.Info("got todo with id %d", todo.Id)
// Return a Json response object with information from the Todo struct
return httpserver.NewJsonResponse(todo), nil
}
Now, you'll walkthrough this file in detail to learn how it works.
Import dependencies
At the top of handler.go
, you declared the package and imported some dependencies:
package main
import (
"context"
"time"
"github.com/justtrackio/gosoline/pkg/cfg"
"github.com/justtrackio/gosoline/pkg/httpserver"
"github.com/justtrackio/gosoline/pkg/log"
)
Here, you declared the package as main
. Then, you imported the context
and time
modules along with three gosoline dependencies:
Define a Todo
Next, you created a Todo
struct:
type Todo struct {
Id int `form:"id"`
Text string `form:"text"`
CreatedAt time.Time
}
Later in this file, you'll use this to bind the data from the HTTP querystring. Referring back to the specification for your todo service, the expected querystring looks like this:
?id={ID}&text={TODO TEXT}
So, in your struct, you have an Id
and a Text
. With the form:
tag, you've specified the querystring parameter from which to pull the value for these keys.
Create a TodoHandler
Next, you created a struct for handling todos:
type TodoHandler struct {
logger log.Logger
}
This is a simple structure that holds a log.Logger
reference.
Get request inputs
Next, you created a function called GetInput()
that returns the input instance to use:
func (t TodoHandler) GetInput() interface{} {
return &Todo{}
}
This is required because your TodoHandler
must implement the GetInput()
method of the httpserver.HandlerWithInput
interface.
Handle new todos
Next, you created a function called NewTodoHandler()
:
func NewTodoHandler(ctx context.Context, config cfg.Config, logger log.Logger) (*TodoHandler, error) {
handler := &TodoHandler{
logger: logger,
}
return handler, nil
}
The config
and logger
argument types come from gosoline. This function initializes a new TodoHandler
and assigns it the logger
. Then, it returns the handler with no error.
Handle requests
Finally, you created a Handle()
function that accepts a context.Context
and an httpserver.Request
(also from gosoline) and handles that request:
func (t TodoHandler) Handle(ctx context.Context, request *httpserver.Request) (*httpserver.Response, error) {
// Initialize a Todo struct from the request body
todo := request.Body.(*Todo)
// Set its CreatedAt to now
todo.CreatedAt = time.Now()
// Log the request using the TodoHandler struct's logger
t.logger.Info("got todo with id %d", todo.Id)
// Return a Json response object with information from the Todo struct
return httpserver.NewJsonResponse(todo), nil
}
Here, you handle requests and responses in just a few lines of code. handler.go
does most of the heavy lifting for your web service. However, you still need a main entry point to your service, where you'll make use of the logic in handler.go
. This, you'll add in main.go
.
Implement main.go
In main.go
, add the following code:
main.go
package main
import (
"context"
"fmt"
"github.com/justtrackio/gosoline/pkg/application"
"github.com/justtrackio/gosoline/pkg/cfg"
"github.com/justtrackio/gosoline/pkg/httpserver"
"github.com/justtrackio/gosoline/pkg/log"
)
func main() {
// Initialize an HTTP server factory that defines your HTTP route
definer := func(ctx context.Context, config cfg.Config, logger log.Logger) (*httpserver.Definitions, error) {
// Initialize a reference to httpserver.Definitions, which you use to create a GET route
def := &httpserver.Definitions{}
// Instantiate two new variables for handling errors and request input data
var err error
var handler httpserver.HandlerWithInput
// Create a handler (`NewTodoHandler`) that handles the request input data.
// If there is an error, return an error message.
// If there is no error, assign this `NewTodoHandler` to the `handler` variable from the last step.
if handler, err = NewTodoHandler(ctx, config, logger); err != nil {
return nil, fmt.Errorf("can not create trip handler: %w", err)
}
// Create a GET route for the endpoint /todo, using the handler
def.GET("/todo", httpserver.CreateQueryHandler(handler))
// Return the response from handler
return def, nil
}
// Run an HTTP server application based on the logic from the previous steps
application.RunHttpDefaultServer(definer)
}
Now, you'll walkthrough this file in detail to learn how it works.
Import dependencies
In main.go
, you declared the package and imported some dependencies:
package main
import (
"context"
"fmt"
"github.com/justtrackio/gosoline/pkg/application"
"github.com/justtrackio/gosoline/pkg/cfg"
"github.com/justtrackio/gosoline/pkg/httpserver"
"github.com/justtrackio/gosoline/pkg/log"
)
Here, you declared the package as main
. Then, you imported the context
and time
modules along with four gosoline dependencies:
application
cfg
httpserver
log
Create your main entry point
Next, you created your main()
function as the entry point to your service:
func main() {
// Initialize an HTTP server factory that defines your HTTP route
definer := func(ctx context.Context, config cfg.Config, logger log.Logger) (*httpserver.Definitions, error) {
// Initialize a reference to httpserver.Definitions, which you use to create a GET route
def := &httpserver.Definitions{}
// Instantiate two new variables for handling errors and request input data
var err error
var handler httpserver.HandlerWithInput
// Create a handler (`NewTodoHandler`) that handles the request input data.
// If there is an error, return an error message.
// If there is no error, assign this `NewTodoHandler` to the `handler` variable from the last step.
if handler, err = NewTodoHandler(ctx, config, logger); err != nil {
return nil, fmt.Errorf("can not create trip handler: %w", err)
}
// Create a GET route for the endpoint /todo, using the handler
def.GET("/todo", httpserver.CreateQueryHandler(handler))
// Return the response from handler
return def, nil
}
// Run an HTTP server application based on the logic from the previous steps
application.RunHttpDefaultServer(definer)
}
main()
puts together all the data structures and logic from handler.go
into a single, coherent function.
Now, you've implemented your server's main entry point and handler logic. Next, you'll configure your server.
Configure your server
In config.dist.yml
, configure your server:
env: dev
app_project: gosoline
app_family: get-started
app_group: http-server
app_name: example
Here, you set some minimal configurations for your web server. The final step is to test it to confirm that it works as expected.
Test your gosoline server
From the todo
directory, start your go module, install the dependencies, and run your server:
go mod init todo/m
go mod tidy
go run .
In your console, you'll see a log stream that indicates your server is running properly. By default, the server is exposed to localhost:8080.
Make a GET request to localhost:8080/todo
. For example:
curl 'localhost:8080/todo?id=1&text=do_it'
You'll get the following output:
{"Id":1,"Text":"do_it","CreatedAt":"2023-09-05T16:55:49.02692+02:00"}
Conclusion
That's it! You created your first gosoline web service. In this tutorial, you were able to:
- Handle requests
- Send Json-serialized responses
- Log information
- And more...
Check out these resources to learn more about creating web services with gosoline: