https://medium.com/@petrousov/how-to-build-a-restful-api-in-go-for-phonebook-app-d55f7234a10

----------------------------------------

How to build a RESTful API in Go for phonebook app

Sep 27, 2018

TL;DR

In this tutorial I am going to show you how I created a RESTful API for a hypothetical phonebook application and how you can create your own APIs based on this example. All the code is stored on github.

Disclaimer

  • This is a project to learn Go myself
  • The storage of data (database) and file structure is out of the scope of this tutorial and was not implemented

Phonebook API

A phonebook application stores records of peoples contact information.

The models

In our context, a person’s record includes the first and last names and contact details such as the city, the zipcode and the phone number. To model this in Go we are going to write the following structs and create a slice where we are going to store our records.

package main

import (
"encoding/json"
)
type Person struct {
ID string `json:"id,omitempty"`
Firstname string `json:"firstname,omitempty"`
Lastname string `json:"lastname,omitempty"`
Contactinfo `json:"contactinfo,omitempty"`
}
type Contactinfo struct {
City string `json:"city,omitempty"`
Zipcode string `json:"Zipcode,omitempty"`
Phone string `json:"phone,omitempty"`
}
var people []Person

There are a couple of things worth mentioning about the above snippet. The fields of the structs need to begin with an uppercase letter so they can be exported. This is necessary because the JSON library we are going to use to encode/decode our data can only access exported values. See https://blog.golang.org/json-and-go for more details.

The other thing worth mentioning is the parameter next to our field types enclosed in backquotes. This is a special parameter which specifies the name of the key our fields are going to have in the JSON format. For example, the value of the field Firstname in our struct will be referenced with the key firstname in the JSON format. For more information, checkout the Marshal() function from the official documentation https://golang.org/pkg/encoding/json/#Marshal.

The handlers

Our backend needs to be able to perform the following 5 operations on our records.

  1. retrieve the records of all the people
  2. retrieve the record of a specific person
  3. create a new person record in the catalog
  4. update a person’s record information
  5. delete a person’s record from the catalog

We are going to analyze the function used to update a person’s information (4) since the rest follow a similar implementation. Given the updated record of a person, this handler will look for this person in our slice and if it finds a matching id, will update the record.

func UpdatePersonEndpoint(w http.ResponseWriter, r *http.Request) {
var person Person
_ = json.NewDecoder(r.Body).Decode(&person)
params := mux.Vars(r)
for i, p := range people {
if p.ID == params["id"] {
people[i] = person
json.NewEncoder(w).Encode(person)
break
}
}
}

The first thing we must notice is the that this function’s name starts with an uppercase letter which means it’s exported. This is not necessary for our example since we store everything in one main.go file. However, if we had a separate file or package called handlers, we would need to be able to call those handlers from a different namespace (main). This is only possible if we have them exported.

All of our functions/handlers accept the same 2 parameters (wr). These parameters represent data streams which our handlers use to retrieve information from (r) and send information to (w). Consider them as the STDIO (keyboard and monitor) of our backend. It’s not necessary to know the implementation of these interfaces, but if you are curious, check out the official documentation https://golang.org/pkg/net/http/#ResponseWriter

In order to implement communication through these data streams, we use two assistive functions, json.NewDecoder() and json.NewEncoder(). These functions allow us to send and receive our data.

The first function is associated with the data stream we use to read from (r) and returns a decoder element. Using the Decode() function on this element, we retrieve the information from the body of a HTTP request and store it in the person variable we created. This information is in JSON, which is human readable format, so we “decode” it into our struct which is readable by our server. A struct variable is a “pass by value” element, so we need pass the address of the variable person to the Decode() function so it can store the values in it.

The second function is associated with the stream we use to write information to (w) and returns an encoder element. Using the Encode() function on this element, we respond to a HTTP request. So, we transform our person variable into JSON and send it back to the responder.

If needed, checkout the docs for more information on the above functions https://golang.org/pkg/encoding/json/#NewDecoder

The last thing to mention about the update handler is that it identifies the record to update by it’s id which is passed as a parameter through the URL when we make the HTTP request. We extract all the variables from the URL using the mux.Vars() function, which returns a map, and reference them using their keys.

 

The rest of the handlers use the same components to implement our API’s functionality.

func GetPeopleEndpoint(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(people)
}
func GetPersonEndpoint(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
for _, p := range people {
if p.ID == params["id"] {
json.NewEncoder(w).Encode(p)
return
}
}
json.NewEncoder(w).Encode("Person not found")
}
func CreatePersonEndpoint(w http.ResponseWriter, r *http.Request) {
var person Person
_ = json.NewDecoder(r.Body).Decode(&person)
people = append(people, person)
json.NewEncoder(w).Encode(person)
}
func DeletePersonEndpoint(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
for i, p := range people {
if p.ID == params["id"] {
copy(people[i:], people[i+1:])
people = people[:len(people)-1]
break
}
}
json.NewEncoder(w).Encode(people)
}

The router

We now have our models and handlers which are able to receive and respond to HTTP requests and convert the data from JSON into our models and back. The next thing we need to implement is the mapping which shows the correspondence of a URL and HTTP request type to our handlers.

  1. /people (GET) -> GetPeopleEndpoint()
  2. /people/{id} (GET) -> GetPersonEndpoint()
  3. /people (POST) -> CreatePersonEndpoint()
  4. /people/{id} (PUT) -> UpdatePersonEndpoint()
  5. /people/{id} (DELETE) -> DeletePersonEndpoint()

This mapping shows that an HTTP GET call to the /people URL will execute the GetPeopleEndpoint() handler. Another HTTP PUT call to /people/{id} will execute the UpdatePersonEndpoint() handler so on and so forth.

For the implementation of the router, we are going to use the gorilla/mux package and write the following code.

import (
"encoding/json"
"net/http" "github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
router.HandleFunc("/people", CreatePersonEndpoint).Methods("POST")
router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
router.HandleFunc("/people/{id}", UpdatePersonEndpoint).Methods("PUT")
}

The logic is pretty straightforward, we initially create a new router instance. Then, we proceed to map our URL endpoints to the handlers we wrote earlier. As we can see, our handlers now have also the HTTP method they require in order to be called defined with the Methods() function.

All these functions are provided by the mux package and its documentation can be found online http://www.gorillatoolkit.org/pkg/mux

Populating with dummy data

For the sake of simplicity we are not going to use a database to store our data. Instead, everything will be stored locally in our slice named people. So, in order to populate our API with some dummy data, we are going to create a couple of entries.

people = append(people, Person{ID: "1", Firstname: "Bruce", Lastname: "Wayne", Contactinfo: Contactinfo{City: "Gotham", Zipcode: "735", Phone: "012345678"}})
people = append(people, Person{ID: "2", Firstname: "Clark", Lastname: "Kent", Contactinfo: Contactinfo{City: "Metropolis", Zipcode: "62960", Phone: "9876543210"}})
}

The server

The last thing left to complete our API is to make it accessible from the network, in other words serve it. To accomplish this, we are going to use the ListenAndServe() function from the http package which starts a HTTP server.

package main

import (
"fmt"
"log"
"net/http" "github.com/gorilla/mux"
)
func main() {
fmt.Println("Starting server on port 8000...")
log.Fatal(http.ListenAndServe(":8000", router))
}

Our server is going to be listening on port 8000. The last line wraps the server function in a log function which will print an error message and return a non-zero code (1) if something goes wrong. The documentation for it can be found online https://golang.org/pkg/log/#Fatal

Testing

A working version of our API is available online from github. Let’s fire up our server by running the go run command.

go run main.go
Starting server on port 8000...

For our tests, we are going to use Postman and fire up all the HTTP requests to confirm the functionality of our handlers.

  1. Retrieve the records of all the people (GET)
 

2. Retrieve the record of a specific person (GET)

 

3. Create a new person record in the catalog (POST)

 

4. Update a person’s record information (PUT)

 

5. Delete a person’s record from the catalog (DELETE)

 

Delete a person’s record using it’s id

Conclusion

In this post I showed you how you can build a simple API in Go which can respond to HTTP requests. Following along you should be able to modify the phonebook API to serve your purpose and follow the documentation if necessary to clear some clouds.

References

golang restful api的更多相关文章

  1. Go实战--通过gin-gonic框架搭建restful api服务(github.com/gin-gonic/gin)

    生命不止,继续 go go go !!! 先插播一条广告,给你坚持学习golang的理由: <2017 软件开发薪酬调查:Go 和 Scala 是最赚钱的语言> 言归正传! 之前写过使用g ...

  2. 使用 Beego 搭建 Restful API 项目

    1 环境准备 首先你需要在你的环境安装以下软件: go:编程语言运行环境 git:版本控制工具 beego:go 语言流行的开发框架 bee:beego 配套的快速搭建工具 你喜欢的数据库:这里以 M ...

  3. RESTful 架构 && RESTful API

    RESTful 架构 && RESTful API REpresentational State Transfer (REST) 具象状态传输https://en.wikipedia. ...

  4. (转载) RESTful API 设计指南

    作者: 阮一峰 日期: 2014年5月22日 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备......). 因此,必须有一种统一的机制 ...

  5. Node.js实现RESTful api,express or koa?

    文章导读: 一.what's RESTful API 二.Express RESTful API 三.KOA RESTful API 四.express还是koa? 五.参考资料 一.what's R ...

  6. Restful Api 最佳实践

    Web APIs has become an very important topic in the last year. We at M-Way Solutions are working ever ...

  7. 基于轻量型Web服务器Raspkate的RESTful API的实现

    在上一篇文章中,我们已经了解了Raspkate这一轻量型Web服务器,今天,我们再一起了解下如何基于Raspkate实现简单的RESTful API. 模块 首先让我们了解一下"模块&quo ...

  8. RESTful Api 身份认证安全性设计

    REST是一种软件架构风格.RESTful Api 是基于 HTTP 协议的 Api,是无状态传输.它的核心是将所有的 Api 都理解为一个网络资源.将所有的客户端和服务器的状态转移(动作)封装到 H ...

  9. 深入理解 RESTful Api 架构

    转自https://mengkang.net/620.html 一些常见的误解 不要以为 RESTful Api  就是设计得像便于 SEO 的伪静态,例如一个 Api 的 URL 类似于 http: ...

随机推荐

  1. centOS 安装 pm2

    安装 npm install -g pm2 自启 pm2 startup centos 启动 pm2 start server/add.js (以实际路径为准) 其他命令: pm2 list #查看进 ...

  2. lua程序设计 第一章习题答案

    练习1.1:运行阶乘的示例并观察,如果输入负数,程序会出现什么问题?试着修改代码来解决问题. 答:当输入负数时,循环无法终止,因为原本程序中的终止条件为n==0,而在输入为负数情况下,无法达成此终止条 ...

  3. 【gcd】辗转相除法

    #include<stdio.h> int gcd(int a, int b) { int c; while(b) { c = a % b; a = b; b = c; } return ...

  4. AndroidStudio下载安装教程(图文教程)

    场景 Android Studio 中文社区: http://www.android-studio.org/ 下载安装包,这里选择64位Windows 等待下载完成. 注: 博客: https://b ...

  5. taglib block

    新建文件 package com.augmentum.oes.taglib; import javax.servlet.jsp.JspException; import javax.servlet.j ...

  6. Netty源码剖析-启动服务

    参考文献:极客时间傅健老师的<Netty源码剖析与实战>Talk is cheap.show me the code! --1主线分两步: 一:首先在our thread里,如果写在mai ...

  7. Prime Number(CodeForces-359C)【快速幂/思维】

    题意:已知X,数组arr[n],求一个分式的分子与分母的最大公因数.分子为ΣX^arr[i],分母为X^Σarr[i],数组为不递减序列. 思路:比赛的时候以为想出了正确思路,WA掉了很多发,看了别人 ...

  8. 20190724-Python网络数据采集/第 2 章 复杂HTML解析-导航树/正则表达式

    1. 导航树 经典的HTML树状结构 直接看下面的代码示例:(注意目标网页的标签大小写等细节,易出bug) from urllib.request import urlopen from bs4 im ...

  9. IDEA中安装go插件,如何能够配置go SDK?

    最近在学习go语言,一个是因为区块链的技术热潮,另一个是接手的项目有用到go写多线程高并发,因此决定自学go. 第一个遇到的问题就是环境! 通过一个晚上的摸索,大概步骤如下: 在IDEA中先打开set ...

  10. 复习最短路 spfa+dijstra堆优化

    题目很简单,, 但是wa了三次,, 用<vector>之前一定要记得clear()...简单说下 spfa的问题 和bell_forman有点类似 每次取出一个点 然后更新 并把更新了的节 ...