home

Todo Lists App

Here I'll describe the basic requirements of this app.

From these requirements, I created the Go language api definition with in these directory structure:

the api.go file will look like this:

package todoapi

type TodoList struct {
    Id   string
    Name string
}

type TodoItem struct {
    Id      string
    ListId  string
    Content string
    Done    bool
}

type AppService interface {
    GetUserService(email string, password string) (service UserService, err error)
}

type UserService interface {
    GetTodoLists() (list []*TodoList, err error)
    GetTodoItems(listId string) (list []*TodoItem, err error)
    PutTodoList(name string) (err error)
    CreateTodo(listId string, content string) (err error)
    DoneTodo(todoItemId string) (err error)
    UndoneTodo(todoItemId string) (err error)
}

Which you can see it's simple Go structs and interfaces. But without logic implementations. Because we want to define our system API interfaces, this file will give the user of the API enough information for what you can do with the API.

Take GetTodoLists for example, client side could use email and password to get a UserService, Which is a way of authentication, that invalid email with password combination can't get UserService, So there is no way to invoke the GetTodoLists api method.

And the GetTodoLists describe clearly that it takes no arguments, But could return a list of TodoList, the TodoList struct includes Name and Id properties.

If I want to invoke the API with JavaScript, I will imagine It would be nice If I can do:

userservice.GetTodoLists(function(list, err){
    for(var i=0; i<list.length; i++){
                console.log(list[i].Name)
            }
});

Because the GetTodoLists definition have two return values, list and err, So we can see the JavaScript callback that have two arguments. that mapping directly to the Go return values.

The hypermusk command that could generate javascript libraries that can do exactly that.

hypermusk -pkg=github.com/hypermusk/todoapp/todoapi -lang=javascript -outdir=./web

The -pkg passed in the Go package of the API definition, -lang tells it you want to generate client side library to use to call the API remotely by using JavaScript. and the -outdir will tell it where to generate the file.

So If I ask you how to call the method CreateTodo in JavaScript?

The answer would be:

userservice.CreateTodo("1", "new todo", function(err){
    console.log(err);
})

And to call the API you defined in Go in another language like Objective C, or Java is as easy as do it in JavaScript. But following the language's idioms.

The server side implementation

But If you really call the API with JavaScript, It won't do anything, Because we only defined the interfaces, didn't do any implementation yet.

The implementation of the server side API must be in Go, and it will import the api package to make sure the implementation is following the definition exactly.

First, we will use the hypermusk tool again to generate the glue code to expose those API definition to the outside world through json (at the current stage, we only support expose json through http).

hypermusk -pkg=github.com/hypermusk/todoapp/todoapi -impl=github.com/hypermusk/todoapp/server -lang=server -outdir=./server

We can see it still uses the todoapi package, because that's the specification of the app. But it adds another argument -impl, Which will hook up the manually implemented package github.com/hypermusk/todoapp/server with the generated glue code. We will write the implementation like this:

type AppServiceImpl struct {
}

type UserServiceImpl struct {
}

func (a *AppServiceImpl) GetUserService(email string, password string) (service todoapi.UserService, err error) {
    if email != "admin@example.com" && password != "nimda" {
        err = errors.New("wrong credentials")
        return
    }
    service = new(UserServiceImpl)
    return
}

func (u *UserServiceImpl) GetTodoLists() (list []*todoapi.TodoList, err error) {
    return
}

func (u *UserServiceImpl) GetTodoItems(listId string) (list []*todoapi.TodoItem, err error) {
    return
}

func (u *UserServiceImpl) PutTodoList(name string) (err error) {
    return
}

func (u *UserServiceImpl) CreateTodo(listId string, name string) (err error) {
    return
}

func (u *UserServiceImpl) DoneTodo(todoId string) (err error) {
    return
}

func (u *UserServiceImpl) UndoneTodo(todoId string) (err error) {
    return
}

var DefaultAppService = new(AppServiceImpl)

The last variable DefaultAppService is the secret to hook up the generated code with our API implementation. Since we defined a AppService interface that no other methods returns it. and It has a method called GetUserService that returns the other service, So it's the root.

Notice that we write a dummy implementation of GetUserService to only return the UserService when password is nimda. In there we could validate the user with database and try to get the user's id into UserServiceImpl, So that it can be used to query user's own todo lists. But right now let's say the system only works for the user admin@example.com.

Next we implement the GetTodoLists:

func (u *UserServiceImpl) GetTodoLists() (list []*todoapi.TodoList, err error) {
    withdb(func(db *sql.DB) {
    list = make([]*todoapi.TodoList, 0)
    rows, err := db.Query("SELECT id, name FROM todo_lists ORDER BY id ASC")
    panicIf(err)

    for rows.Next() {
        newL := new(todoapi.TodoList)
        var id int
        err = rows.Scan(&id, &newL.Name)
        panicIf(err)
        newL.Id = fmt.Sprintf("%d", id)
        list = append(list, newL)
    }
    })
    return
}

It first query from the postgresql table todo_lists, and then populate them into the struct todoapi.TodoList, It's quite straight forward.

Next thing we do is create a http server to serve the API:

package main

import (
    "github.com/hypermusk/todoapp/server/todoapihttpimpl"
    "log"
    "net/http"
)

func main() {

    todoapihttpimpl.AddToMux("/api", http.DefaultServeMux)
    http.DefaultServeMux.Handle("/web/", http.FileServer(http.Dir("../../")))
    err := http.ListenAndServe(":9000", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

The todoapihttpimpl package is the glue code that hypermusk generated. And it has a AddToMux method that could expose all those APIs through a url like /api/UserService/GetTodoLists.json

After this, you start the server with go run main.go, And if there is no compile errors, You should be able to serve the APIs to your javascript, here is the proof.

And how the http round trip is if you want to know the internal of how it works:

And the full code of this app is at: https://github.com/hypermusk/todoapp

Next: Make the Todo List Web app with these API and Angularjs