Wednesday, October 12, 2022
HomeWeb DevelopmentConstruct an online app in Go together with Copper

Construct an online app in Go together with Copper


Copper is an all-inclusive Go toolbox for creating internet functions with much less boilerplate and excessive deal with developer effectivity, which makes constructing internet apps in Go extra fascinating and enjoyable.

Copper nonetheless depends on the Go commonplace library to take care of the standard Go expertise whereas permitting you construct frontend apps alongside along with your backend and ship every part in a single binary. It has assist for constructing full-stack internet apps with React and Tailwind within the frontend, and it additionally helps constructing APIs that give extra flexiblity to work with different frontend frameworks like Vue and Svelte.

On this article, we shall be having a look at how we are able to construct an online utility in Go together with the gocopper framework so you possibly can see the way you’d implement it in your individual tasks.

Stipulations

To get together with this tutorial, you need to have the next:

  • Go v1.16+ put in
  • Node v16+ put in
  • Expertise constructing Golang functions

Set up

To get began with Copper, we’ll run the next command on our terminal:

$ go set up github.com/gocopper/cli/cmd/[email protected]

Run the next command to make sure your Copper set up works accurately:

$ copper -h

If copper -h didn’t work, it most likely signifies that $GOPATH/bin just isn’t in your $PATH. Add the next to ~/.zshrc or ~/.bashrc:

export PATH=$HOME/go/bin:$PATH

Then, restart your terminal session and take a look at copper -h once more.

Configuration

Copper means that you can configure the mission template utilizing -frontend and -storage arguments.

The -frontend argument means that you can configure your frontend with the next frontend frameworks and libraries:

The -storage argument means that you can configure your database stack, which is sqlite3 by default. You might have choices to set it to postgres, mysql, or skip storage solely with none.

What we shall be constructing: A easy to-do app

We shall be constructing a full-stack internet utility that enables us to carry out CRUD operations on our SQLite database. Principally, we shall be constructing a to-do utility. Right here is how the completed app appears:

Finished App

This utility permits us to get, add, replace, and delete to-do gadgets from our database. With out additional ado, let’s get began.

Venture setup

We’ll create a mission that makes use of the Go templates for frontend utilizing the go frontend stack as follows:

copper create -frontend=go github.com/<your-username>/todolist

With the above command, copper create a fundamental scaffold mission with the Go templates for frontend and sqlite3 for the database.

Right here is how your mission scaffold ought to look:

Project Scaffold

To start out the app server, run the next command:

$ cd todolist && copper run -watch

Open http://localhost:5901 to see the Copper welcome web page.

Updating the structure file

Let’s replace the internet/src/layouts/primary.html file with a script tag as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta identify="viewport" content material="width=device-width, initial-scale=1.0" />
    <title>Copper App</title>
    <hyperlink rel="stylesheet" href="https://weblog.logrocket.com/static/types.css" />
  </head>
  <physique>
    {{ template "content material" . }}
    <script src="/static/index.js"></script>
  </physique>
</html>

We’ll write some javascript within the static/index.js file to deal with the delete request in our app as we proceed.

Our to-do app requires customers so as to add to-dos to the database. Let’s create a brand new to-dos web page with a easy kind and a piece to checklist all to-do gadgets.

Navigate to the pages listing and create a file referred to as todos.html with the next:

{{ outline "content material"}}
<div class="gr-container">
    <h2 class="heading">Todo Checklist</h2>
    <kind class="add-todo" motion="/todos" methodology="submit">
        <enter sort="textual content" identify="todo">
        <button sort="submit">Add Todo</button>
    </kind>
    <div class="todo-list">
        <kind class="todo-item" motion="" methodology="submit">
            <enter sort="textual content" identify="todo">
            <button class="replace" sort="submit">Replace Todo</button>
            <button class="delete" sort="button">Take away Todo</button>
        </kind>
    </div>
</div>
{{ finish }}

Styling our pattern app

Navigate to internet/public listing and replace model.css with the next:

@import url('https://fonts.googleapis.com/css2?household=Work+Sans:ital,[email protected],700;1,400&show=swap');
html, physique {
    margin: 0;
    background-color: wheat;
}
#app {
    /* background-color: whitesmoke; */
    padding: 40px 0;
    font-family: 'Work Sans', sans-serif;
    text-align: middle;
    peak: 100%;
    box-sizing: border-box;
}
.tagline {
    font-style: italic;
    font-weight: 400;
}
hr {
    margin: 40px auto;
    width: 60%;
    border-top: 1px stable #ddd;
}
.video {
    width: 60%;
    margin: 0 auto;
}
.todo-list {
    background-color: greenyellow;
    margin-top: 2rem;
    width: 60%;
    padding: 1.5rem;
}
.add-todo {
    width: 50%;
    show: flex;
    justify-items: middle;
}
.add-todo enter {
    width: 85%;
    peak: 2rem;
}
.add-todo button {
    width: 15%;
    /* peak: 1.5rem; */
}
.todo-item {
    show: flex;
    margin-bottom: 1rem;
}
.todo-item enter {
    width: 65%;
    peak: 1.6rem;
}
.replace, .delete {
    width: 16%;
}
.heading {
    text-align: middle;
    font-size: 2.5rem;
}
.gr-container {
    padding: 1rem;

    show: flex;
    flex-direction: column;
    align-items: middle;
}

With a view to view the to-dos web page on the browser, we’ll replace the pkg/app/router.go file as follows:

func (ro *Router) Routes() []chttp.Route {
    return []chttp.Route{
        {
            Path:    "https://weblog.logrocket.com/",
            Strategies: []string{http.MethodGet},
            Handler: ro.HandleIndexPage,
        },
        {
            Path:    "/todos",
            Strategies: []string{http.MethodGet},
            Handler: ro.HandleTodosPage,
        },
    }
}
func (ro *Router) HandleTodosPage(w http.ResponseWriter, r *http.Request) {
    ro.rw.WriteHTML(w, r, chttp.WriteHTMLParams{
        PageTemplate: "todos.html",
    })
}

Right here, we replace our app route with the "/todos" path and likewise create the HandleTodosPage handler, which serves todos.html web page when a person hit the "/todos" route within the browser.

Restart the app server and open http://localhost:5901/todos to see the to-dos web page.

Right here is how the to-dos web page ought to look:

Initial Version of App

Implement a characteristic to create now to-do gadgets

To implement the create characteristic, we’d arrange a storage layer that may save the todo gadgets into our database.

Let’s run the next command to create the pkg/todos package deal in addition to a pkg/todos/queries.go that we are able to use to implement our SQL queries:

copper scaffold:pkg todos
copper scaffold:queries todos

Subsequent, we’ll create the Todo mannequin and its database migration. Open up pkg/todos/fashions.go and outline the Todo mannequin:

sort Todo struct {
    Title string
    Rank int64 `gorm:"-"`
}

Then, open up migrations/0001_initial.sql and outline its database schema:

-- +migrate Up
CREATE TABLE todos (
    identify textual content
);
-- +migrate Down
DROP TABLE todos;

In your terminal, run copper migrate to create the desk.

Now that we’ve got a Todo mannequin and a todos desk in our database, we are able to write our database queries within the pkg/posts/queries.go file.

Let’s add a SaveTodo methodology that can be utilized to insert new to-dos into the todos desk:

import (
    "context"
    ...
)
func (q *Queries) SaveTodo(ctx context.Context, todo *Todo) error {
    const question = `
    INSERT INTO todos (identify)
    VALUES (?)`
    _, err := q.querier.Exec(ctx, question,
        todo.Title,
    )
    return err
}

Right here, we use an SQL question to insert a brand new to-do into the DB. The query mark is a placeholder for a price within the question. The q.querier.Exec operate takes a context object, a question, and as many arguments you need to cross into the placeholders. Then, the values within the placeholders are inserted within the order they seem within the q.querier.Exec operate.

Now, let’s head over to pkg/app/router.go and use the SaveTodo methodology to deal with kind submissions for creating new to-dos.

Create a brand new route handler, HandleSubmitPost, to deal with submissions for creating new to-dos as follows:

func (ro *Router) HandleCreateTodos(w http.ResponseWriter, r *http.Request) {
    var (
        todo = strings.TrimSpace(r.PostFormValue(("todo")))
    )
    if todo == "" {
        ro.rw.WriteHTMLError(w, r, cerrors.New(nil, "unable to create todos: todo can't be empty", map[string]interface{}{
            "kind": r.Kind,
        }))
        return
    }
    newtodo := todos.Todo{Title: todo}
    err := ro.todos.SaveTodo(r.Context(), &newtodo)
    if err != nil {
        ro.logger.Error("an error occured whereas saving todo", err)
        ro.rw.WriteHTMLError(w, r, cerrors.New(nil, "unable to create todos", map[string]interface{}{
            "kind": r.Kind,
        }))
        return
    }
    http.Redirect(w, r, "/todos", http.StatusSeeOther)
}

This operate creates a brand new to-do merchandise. First. we get the to-do worth from the shape and trim any white area the person could have added. Then, we verify if the to-do is an empty string, during which case we throw an error. Subsequent, we create a brand new to-do object and name the ro.todos.SaveTodo operate to reserve it into the database. If there was an error whereas saving the to-do, we throw an error. Lastly, we redirect to the to-dos web page.


Extra nice articles from LogRocket:


Replace the pkg/app/router.go file with the HandleCreateTodos as follows:

import (
    "strings"
    "github.com/gocopper/copper/cerrors"
    "github.com/gocopper/copper/chttp"
    "github.com/gocopper/copper/clogger"
    "internet/http"
    "github.com/emmanuelhashy/todolist/pkg/todos"
)

sort NewRouterParams struct {
    Todos  *todos.Queries
    RW     *chttp.ReaderWriter
    Logger clogger.Logger
}

func NewRouter(p NewRouterParams) *Router {
    return &Router{
        rw:     p.RW,
        logger: p.Logger,
        todos:  p.Todos,
    }
}

sort Router struct {
    rw     *chttp.ReaderWriter
    logger clogger.Logger
    todos  *todos.Queries
}

func (ro *Router) Routes() []chttp.Route {
    return []chttp.Route{
        {
            Path:    "https://weblog.logrocket.com/",
            Strategies: []string{http.MethodGet},
            Handler: ro.HandleIndexPage,
        },
        {
            Path:    "/todos",
            Strategies: []string{http.MethodGet},
            Handler: ro.HandleTodosPage,
        },
        {
            Path:    "/todos",
            Strategies: []string{http.MethodPost},
            Handler: ro.HandleCreateTodos,
        },
    }
}

Now, the create characteristic for our todo app ought to work! We will add a brand new to-do into the todos desk.

Add a learn characteristic to see all of the to-do gadgets

To implement the learn characteristic, we’d create a brand new SQL question to return a listing of all todos that exist within the database and work our manner up from there.

Replace pkg/todos/queries.go with the next methodology to checklist all to-dos:

func (q *Queries) ListTodos(ctx context.Context) ([]Todo, error) {
    const question = "SELECT * FROM todos"
    var (
        todos []Todo
        err   = q.querier.Choose(ctx, &todos, question)
    )
    return todos, err
}

Right here, we use an SQL question to get all of the to-dos saved within the database. Since we aren’t updating a price within the DB, we use the q.querier.Choose methodology versus q.querier.Exec.

Subsequent, replace the HandleTodosPage methodology in pkg/app/router.go to question all todos and cross it to the HTML template:

func (ro *Router) HandleTodosPage(w http.ResponseWriter, r *http.Request) {
    todos, err := ro.todos.ListTodos(r.Context())
    if err != nil {
        ro.logger.Error("an error occured whereas fetching todos", err)
        ro.rw.WriteHTMLError(w, r, cerrors.New(nil, "unable to fetch todos", map[string]interface{}{
            "kind": r.Kind,
        }))
    }
    for i := vary todos {
        todos[i].Rank = int64(i + 1)
    }
    ro.rw.WriteHTML(w, r, chttp.WriteHTMLParams{
        PageTemplate: "todos.html",
        Information:         todos,
    })
}

To utilize the information and render a listing of all to-dos, replace internet/src/pages/todos.html as follows:

{{ outline "content material"}}
<div class="gr-container">
    <h2 class="heading">Todo Checklist</h2>
    <kind class="add-todo" motion="/todos" methodology="submit">
        <enter sort="textual content" identify="todo">
        <button sort="submit">Add Todo</button>
    </kind>
    <div class="todo-list">
        {{ vary . }}
        <kind class="todo-item" motion="/{{.Title}}" methodology="submit">
            {{.Rank}}.
            <enter sort="textual content" identify="todo" worth="{{.Title}}">
            <button class="replace" sort="submit">Replace Todo</button>
            <button class="delete" sort="button">Take away Todo</button>
        </kind>

        {{ finish }}
    </div>
</div>
{{ finish }}

Now, you need to see a listing of all of the to-dos within the database within the to-dos web page.

List of Todos

Replace to-do gadgets

To implement the replace characteristic, we’ll create a brand new SQL question to replace to-dos that exist within the database.

Replace pkg/todos/queries.go with the next methodology:

func (q *Queries) UpdateTodo(ctx context.Context, oldName string, todo *Todo) error {
    const question = `
    UPDATE todos SET identify=(?) WHERE identify=(?)`
    _, err := q.querier.Exec(ctx, question,
        todo.Title,
        oldName,
    )
    return err
}

Right here, we use an SQL question to replace a to-do worth within the DB. We cross in new and previous values respectively to the placeholder within the SQL question.

Subsequent, we’ll create a HandleUpdateTodos methodology in pkg/app/router.go to replace present todos:

func (ro *Router) HandleUpdateTodos(w http.ResponseWriter, r *http.Request) {
    var (
        todo    = strings.TrimSpace(r.PostFormValue("todo"))
        oldName = chttp.URLParams(r)["todo"]
    )
    if todo == "" {
        ro.rw.WriteHTMLError(w, r, cerrors.New(nil, "unable to replace todos: todo can't be empty", map[string]interface{}{
            "kind": r.Kind,
        }))
        return
    }
    newtodo := todos.Todo{Title: todo}
    ro.logger.WithTags(map[string]interface{}{
        "oldname": oldName,
        "newname": newtodo.Title,
    }).Information("Todo up to date")
    err := ro.todos.UpdateTodo(r.Context(), oldName, &newtodo)
    if err != nil {
        ro.logger.Error("an error occured whereas saving todo", err)
        ro.rw.WriteHTMLError(w, r, cerrors.New(nil, "unable to replace todos", map[string]interface{}{
            "kind": r.Kind,
        }))
        return
    }
    http.Redirect(w, r, "/todos", http.StatusSeeOther)
}

Then replace Routes with the HandleUpdateTodos methodology as follows:

func (ro *Router) Routes() []chttp.Route {
    return []chttp.Route{
        ...
        {
            Path:    "/{todo}",
            Strategies: []string{http.MethodPost},
            Handler: ro.HandleUpdateTodos,
        },
    }
}

Delete to-dos

To implement the delete characteristic, we’ll create a brand new SQL question to delete to-dos that exist within the database.

Replace pkg/todos/queries.go with the next methodology:

func (q *Queries) DeleteTodo(ctx context.Context, todo *Todo) error {
    const question = `
    DELETE from todos WHERE identify=(?)`
    _, err := q.querier.Exec(ctx, question,
        todo.Title,
    )
    return err
}

Subsequent, we’ll create HandleDeleteTodos methodology in pkg/app/router.go to delete present to-dos:

sort error struct {
    error string
}

func (ro *Router) HandleDeleteTodos(w http.ResponseWriter, r *http.Request) {
    var (
        todo = strings.TrimSpace(chttp.URLParams(r)["todo"])
    )
    if todo == "" {
        deleteError := error{error: "Unable to delete todo"}
        ro.rw.WriteJSON(w, chttp.WriteJSONParams{StatusCode: 500, Information: deleteError})
        return
    }
    newtodo := todos.Todo{Title: todo}
    err := ro.todos.DeleteTodo(r.Context(), &newtodo)
    if err != nil {
        ro.logger.Error("an error occured whereas deleting todo", err)
        deleteError := error{error: "Unable to delete todo"}
        ro.rw.WriteJSON(w, chttp.WriteJSONParams{StatusCode: 500, Information: deleteError})
        return
    }
    http.Redirect(w, r, "/todos", http.StatusSeeOther)
}

Then replace Routes with the HandleDeleteTodos methodology as follows:

func (ro *Router) Routes() []chttp.Route {
    return []chttp.Route{
        ...
        {
            Path:    "/{todo}",
            Strategies: []string{http.MethodDelete},
            Handler: ro.HandleDeleteTodos,
        },
    }
}

To wrap up the delete characteristic of our to-do app, we’ll create an index.js file in internet/public listing and add the next operate:

operate deleteTodo(identify){
    fetch(`/${identify}`, {methodology: "Delete"}).then(res =>{
        if (res.standing == 200){
            window.location.pathname = "/todos"
        }
    }).catch(err => {
        alert("An error occured whereas deleting the todo", err.message)
    })
}

Then, replace the take away to-do button in pages/todos.html as follows:

<button class="delete" sort="button" onclick = "deleteTodo('{{.Title}}')" >Take away Todo</button>

We’ve got efficiently constructed an online app in Go utilizing gocopper.

In case you adopted the above tutorial accurately, your to-do app ought to be capable to carry out fundamental create, learn, replace, and delete operations.

Right here is the hyperlink to the ultimate code repository.

Conclusion

This tutorial has lastly come to an finish. We checked out how you can create an online utility in Go utilizing Copper, and we used this to construct a to-do utility efficiently.

Copper makes use of google/wire to allow on-the-fly dependency injection. It comes with the clogger package deal, which allows structured logging in each growth and manufacturing environments. Additionally, it really works carefully with the cerrors package deal to assist add construction and context to your errors, leading to considerably improved error logs.

I can’t wait to see what you construct subsequent as a result of there are such a lot of methods to make this higher. You could comply with me @5x_dev on Twitter.

: Full visibility into your internet and cell apps

LogRocket is a frontend utility monitoring resolution that allows you to replay issues as in the event that they occurred in your individual browser. As an alternative of guessing why errors occur, or asking customers for screenshots and log dumps, LogRocket permits you to replay the session to shortly perceive what went mistaken. It really works completely with any app, no matter framework, and has plugins to log further context from Redux, Vuex, and @ngrx/retailer.

Along with logging Redux actions and state, LogRocket information console logs, JavaScript errors, stacktraces, community requests/responses with headers + our bodies, browser metadata, and customized logs. It additionally devices the DOM to file the HTML and CSS on the web page, recreating pixel-perfect movies of even essentially the most complicated single-page and cell apps.

.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments