当前位置:网站首页>Go language library management restful API development practice

Go language library management restful API development practice

2022-06-25 06:26:00 InfoQ

Go(Golang) It's popular recently , And a relatively new programming language .

It is small and stable , Easy to use and learn , Fast , Compiled ( Native code ), And a large number of cloud tools and services (Docker、Kubernetes...).

Considering all the benefits it brings , There is no reason not to try .

In this tutorial , We will build a simple book store REST API.

Programmer's treasure house
:https://github.com/Jackpopc/CS-Books-Store

1.  preparation

Before we start , We need to do some preparatory work in advance :

  • browser
  • gorilla/handlers
  • gorilla/mux

Get ready for this , We can start Go On a journey !

2.  Application structure

You should have installed... By now Go, And made preparations in advance .

Open your favorite IDE(Visual Studio Code, GoLand, ...), Create a new project .

As I mentioned earlier , Our idea is to use Mux Build a simple REST API, Used for book store management .

Once you create your blank project , Create the following structure :

├── main.go
└── src
 ├── app.go
 ├── data.go
 ├── handlers.go
 ├── helpers.go
 └── middlewares.go

Go Toolkits and modules

So let's see Go Modules and packages , If you are familiar with Python, You may have an idea of these things , Because their operations are very similar .

describe  Go  The best way to pack is , It is a collection of source files in the same directory , Compiled into a reusable unit .

This means that all documents with similar purposes should be put in one package .

According to our above structure ,
src
It's one of our bags .

Go The module is Go A collection of packages and their dependencies , This means that a module can consist of multiple packages .

For the sake of understanding , You can think of our entire application as a Go modular .

Let's execute this command in the project root directory to create our module .

go mod init bookstore

You should see a new file in your root directory , be known as
go.mod
.

3.  structure API

Now it's time to start building our application .

Open your
main.go
file , Insert the following code .

package main

import"bookstore/src"

func main() {
 src.Start()
}

We declare our Lord Go package (
package main
), And put our
src
Packages and modules
bookstore
Import together with the prefix of .

In function
main()
in , We will run the package
src
Of
Start()
function .

This is our entry file (
main.go
) My sole responsibility -- start-up API.

Routes and handlers

Now we need to create our API Router (
Mux
), And configure it by creating some endpoints and their handlers .

In your src Open... In the package
app.go
And insert the following code .

package src

import (
 "github.com/gorilla/handlers"
 "github.com/gorilla/mux"
 "log"
 "net/http"
 "os"
)

func Start() {
 router := mux.NewRouter()
 router.Use(commonMiddleware)
 router.HandleFunc("/book", getAllBooks).Methods(http.MethodGet)
 router.HandleFunc("/book", addBook).Methods(http.MethodPost)
 router.HandleFunc("/book/{book_id:[0-9]+}", getBook).Methods(http.MethodGet)
 router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)
 router.HandleFunc("/book/{book_id:[0-9]+}", deleteBook).Methods(http.MethodDelete)
 log.Fatal(http.ListenAndServe("localhost:5000", handlers.LoggingHandler(os.Stdout, router)))
}

As you can see , We declare
app.go
yes src Part of the package , It contains us in
main.go
The
Start()
function .

We also import two external modules , We need what the program needs to rely on
mux
and
handlers
.

Execute the following commands in your terminal :

go get github.com/gorilla/handlers
go get github.com/gorilla/mux

Yours
go.mod
The files should also be synchronized , Now it should be like this :

module bookstore

go1.17

require (
 github.com/gorilla/handlers v1.5.1
 github.com/gorilla/mux v1.8.0
)

require github.com/felixge/httpsnoop v1.0.1// indirect

Let's take a closer look at our
Start()
function .

First , We announced a new
Mux
Router variables , It will be responsible for routing and processing the entire API Request .

then , We tell
Mux
, We need to use a middleware , It will come to us at every API Execute the following line in your request :

router.Use(commonMiddleware)

There will be more about middleware later .

Continue to analyze our code , We can see where we create endpoints and handlers ( Callback function ) And some original verification , for example :

router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)

Once the user is in
/book/123
( Or any other number ) Use on path
PUT
Method click on our server , This endpoint will start .

then , It will pass the request to
updateBook
Processing function for further processing .

book_id
The variable must be a number , Because we specify a simple validation after the variable name declaration .

Last , We will run our server on a specific combination of hosts and ports , And let it record everything to our terminal .

middleware

We all know ,REST APIs When accepting requests and returning responses, most of them use JSON.

This is by using
Content-Type
Header information to our browser /HTTP Client's .

Because of our API Will only use JSON Data represented , We can use a middleware , To ensure that our content type is always set to JSON.

As mentioned earlier ,
app.go
Of
Start()
Method contains this line :

router.Use(commonMiddleware)

Let's open our
middlewares.go
File and create the required functions :

package src

import"net/http"

func commonMiddleware(next http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 w.Header().Set("content-type", "application/json; charset=utf-8")
 w.Header().Set("x-content-type-options", "nosniff")
 next.ServeHTTP(w, r)
 })
}

Once the user clicks, we are
Start()
Function is directed to Mux Any endpoint registered by the router , The middleware will intercept the request and add us in
commonMiddleware
The two header information specified in the function .

then , It will further pass the modified request to the processing function of the requested endpoint or another middleware .

Static data

Because we won't use any data storage services ( database 、 cache ......), We need some kind of static data .

in addition , We will create a data type for the custom response , I'll explain later .

open
src
The package
data.go
, Put the following in it .

package src

type Book struct {
 Id int`json:"id"`
 Title string`json:"title"`
 Author string`json:"author"`
 Genre string`json:"genre"`
}

var booksDB = []Book{
 {Id: 123, Title: "The Hobbit", Author: "J. R. R. Tolkien", Genre: "Fantasy"},
 {Id: 456, Title: "Harry Potter and the Philosopher's Stone", Author: "J. K. Rowling", Genre: "Fantasy"},
 {Id: 789, Title: "The Little Prince", Author: "Antoine de Saint-Exupéry", Genre: "Novella"},
}

We just created a data structure , It will be in our API Save the information needed for a book in .

I also created json label , If the data type will be in JSON Formal transmission , It will translate the field name into JSON Express . Besides , Also created an original book storage system ( In memory ) And some initial book data (booksDB).

Add this code to the table above :

type CustomResponse struct {
 Code int `json:"code"`
 Message string `json:"message"`
 Description string `json:"description,omitempty"`
}

var responseCodes = map[int]string {
 400: "Bad Request",
 401: "Unauthorized",
 403: "Forbidden",
 404: "Not Found",
 409: "Conflict",
 422: "Validation Error",
 429: "Too Many Requests",
 500: "Internal Server Error",
}

We just made a new data structure , Will unify our API The error that will be returned / Respond to , More on this later .

Auxiliary tool

We will need some auxiliary tools to make full use of our API. for example , We will need to check with a given ID Whether your book exists ( Add a new book , Modify existing books )、 You need to delete a with the given ID The book of ( Delete book )、 Need to be for a given HTTP The status code returns a custom JSON Respond to .

open src In bag
helpers.go
, Insert the following into it :

package src

import (
 "encoding/json"
 "net/http"
)

func removeBook(s []Book, i int) []Book {
 if i != len(s)-1 {
 s[i] = s[len(s)-1]
 }
 return s[:len(s)-1]
}

func checkDuplicateBookId(s []Book, id int) bool {
 for _, book := range s {
 if book.Id == id {
 return true
 }
 }
 return false
}

func JSONResponse(w http.ResponseWriter, code int, desc string) {
 w.WriteHeader(code)
 message, ok := responseCodes[code]
 if !ok {
 message = "Undefined"
 }
 r := CustomResponse{
 Code: code,
 Message: message,
 Description: desc,
 }
 _ = json.NewEncoder(w).Encode(r)
}

removeBook
The function traverses
Book
, If it's not the last element of the fragment , It will move it to the end of the fragment and return a new fragment without it ( Avoid the last element ).

checkDuplicateBookId
The function returns one bool value ( True or false ), It depends on the given id Does it exist in
Book
in .

JSONResponse
The function is responsible for using the
CustomResponse
and
responseCodes
. It will return a CustomResponse Of JSON Express , Which includes responseCodes Status codes and messages to be provided .

such , We will avoid in our API For the same HTTP Status codes have different messages .

The handler

Now comes the last step , Put endpoint handlers together .

Open your
handlers.go
, Let's enter some code in it :

package src

import (
 "encoding/json"
 "github.com/gorilla/mux"
 "net/http"
 "strconv"
)
Get a single book
func getBook(w http.ResponseWriter, r *http.Request) {
 vars := mux.Vars(r)
 bookId, _ := strconv.Atoi(vars["book_id"])
 for _, book := range booksDB {
 if book.Id == bookId {
 _ = json.NewEncoder(w).Encode(book)
 return
 }
 }
 JSONResponse(w, http.StatusNotFound, "")
}

We from Mux The router gets the passed variables , Convert it from a string to int value . then , Traverse our booksDB, Look for matching books ID. If it exists , Go back to it  -  If it doesn't exist , We go back to
404: Not Found
error .
Get all the books
func getAllBooks(w http.ResponseWriter, r *http.Request) {
 _ = json.NewEncoder(w).Encode(booksDB)
}

Is it simple ? take booksDB Convert to JSON, And return it to the user .
Add a new book
func addBook(w http.ResponseWriter, r *http.Request) {
 decoder := json.NewDecoder(r.Body)
 var b Book
 err := decoder.Decode(&b)
 if err != nil {
 JSONResponse(w, http.StatusBadRequest, "")
 return
 }
 if checkDuplicateBookId(booksDB, b.Id) {
 JSONResponse(w, http.StatusConflict, "")
 return
 }
 booksDB = append(booksDB, b)
 w.WriteHeader(201)
 _ = json.NewEncoder(w).Encode(b)
}

Because it's in POST Method triggered , The user must provide in the request body Book Structural JSON data .

{
 "id": 999,
 "title": "SomeTitle",
 "author": "SomeAuthor",
 "genre": "SomeGenre"
}

Once we decode and verify according to our book structure JSON The main body ( If you fail , We will return
400: Bad Request error
), We need to check that we have the same ID Does your book already exist . If so , We will return
409: Conflict error back
. conversely , We will add our... With the book provided by the user booksDB, And its JSON Indicates that it is returned to the user .
Update existing books
func updateBook(w http.ResponseWriter, r *http.Request) {
 vars := mux.Vars(r)
 bookId, _ := strconv.Atoi(vars["book_id"])
 decoder := json.NewDecoder(r.Body)
 var b Book
 err := decoder.Decode(&b)
 if err != nil {
 JSONResponse(w, http.StatusBadRequest, "")
 return
 }
 for i, book := range booksDB {
 if book.Id == bookId {
 booksDB[i] = b
 _ = json.NewEncoder(w).Encode(b)
 return
 }
 }
 JSONResponse(w, http.StatusNotFound, "")
}

And addBook Function handlers are almost the same , But there is a major difference .

To update this book , It must already exist (ID Must be in booksDB in ).

If it exists , We will update the values of existing books , otherwise , We will return
404: Not Found
error .
Delete existing books
func deleteBook(w http.ResponseWriter, r *http.Request) {
 vars := mux.Vars(r)
 bookId, _ := strconv.Atoi(vars["book_id"])
 for i, book := range booksDB {
 if book.Id == bookId {
 booksDB = removeBook(booksDB, i)
 _ = json.NewEncoder(w).Encode(book)
 return
 }
 }
 JSONResponse(w, http.StatusNotFound, "")
}

Before we get book_id After the integer value of the variable , We traverse booksDB, Find the book the user wants to delete .

If it exists , We use our auxiliary function
removeBook
, from Book Delete the book from the structure slice . If it doesn't exist , We will return
404: Not Found
error .

4.  Run and test API

Now our API Already completed , Let's run it , Execute this program on your terminal :

go run main.go

Start your favorite HTTP client (Insomnia, Postman, ...), Try some of the interfaces we created
原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202201236120737.html