برنامه‌نویسی

آموزش ساخت RESTful API با Go


۱۸ اردیبهشت ۱۴۰۰
آموزش ساخت restful api با go

در این مقاله‌ی آموزشی می‌خواهیم با زبان برنامه‌نویسی Go یک RESTful API توسعه دهیم که به کمک آن می‌توانید رویدادها را مدیریت کنید.

پیش‌نیازها

  • فرض ما بر این است که شما از قبل با زبان Go آشنا شده‌اید و در حال حاضر این زبان برنامه‌نویسی بر روی سیستم شما نصب است.
  • پس از نصب زبان Go باید در GOPATH که اکثر افراد تمام پروژه‌هایشان را در این مسیر نگهداری می‌کنند، پروژه‌ی فعلی را توسعه دهیم بنابراین در پوشه‌ی src می‌توانید یک فولدر خاص برای این پروژه ایجاد کنید یا برای ساماندهی بیشتر پروژه‌های مختلف یک پوشه با نام github.com ایجاد کنید و در آن پوشه‌ی دیگری ایجاد کنید و نام آن را همان نام کاربری خود در GitHub قرار دهید. در نهایت در مسیر GOPATH/src/github.com/<Github username> یک پوشه‌ی جدید برای پروژه‌ی فعلی با نام دلخواه ایجاد کنید.

مقاله‌ی مرتبط: توسعه و اجرای اولین برنامه با زبان Go

توسعه‌ی API

برای پیاده‌سازی ساختار اولیه‌ی پروژه می‌توانید به شکل زیر عمل کنید:

mkdir go-rest-api
cd go-rest-api
touch main.go

پس از ایجاد کردن فایل main.go از یک ویرایشگر کد برای توسعه‌ی برنامه کمک می‌گیریم و کدهای زیر را در فایل ایجاد شده قرار می‌دهیم:

package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

برای کامپایل این برنامه ساده می‌توانید در Terminal فعلی دستور go build را اجرا کرده و درنهایت به شکل زیر برنامه‌ی کامپایل شده را اجرا کنید:

./go-rest-api

انتظار می‌رود که خروجی زیر در Terminal شما چاپ شود:

Hello World!

راه‌اندازی سرور HTTP با استفاده از Gorilla Mux

Gorilla Mux یک پکیج است که به شما امکان می‌دهد routeهای مورد نیاز برنامه را ایجاد کرده و هر کدام از درخواست‌های ورودی را به Controller مربوطه ارجاع دهید. قبل از کار با پکیج Mux باید آن را به شکل زیر نصب کرده:

go mod init
go get -u github.com/gorilla/mux

و در مرحله‌ی بعد اولین endpoint برنامه را تعریف کنیم:

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func homeLink(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome home!")
}

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", homeLink)
	log.Fatal(http.ListenAndServe(":8080", router))
}

مطمئنا پس از ایجاد هر تغییری در کدها باید برنامه را مجددا اجرا کنید. حال برای اجرای برنامه دو راه حل وجود دارد که یکی از آن‌ها کامپایل برنامه و اجرای برنامه‌ی کامپایل شده است اما برای سریع‌تر شدن فرایند تست برنامه می‌توانید دستور go run main.go را اجرا کنید که بدون نیاز به کامپایل، سرور توسعه داده شده را برای شما اجرا می‌کند.

خروجی مورد انتظار پس از راه‌اندازی و ارسال درخواست GET به برنامه در آدرس localhost:8080 به شکل زیر است.

راه‌اندازی سرور با استفاده از mux

شبیه‌سازی دیتابیس

برای شبیه‌سازی دیتابیس می‌توانیم رویداد‌ها را به شکل زیر در برنامه تعریف کنیم:

type event struct {
	ID          string `json:"ID"`
	Title       string `json:"Title"`
	Description string `json:"Description"`
}

type allEvents []event

var events = allEvents{
	{
		ID:          "1",
		Title:       "Introduction to Golang",
		Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
	},
}

در این ساختار فقط از ID، Title و Description برای ذخیره‌ی رویدادها استفاده کرده‌ایم و در ادامه کدهای مربوط به اضافه کردن، به‌روزرسانی و حذف رویدادها را پیاده‌سازی خواهیم کرد.

اضافه کردن یک رویداد جدید

برای ایجاد یک رویداد جدید باید داده‌های ارسال شده به‌صورت POST را دریافت کرده و به دیتابیس شبیه‌سازی شده اضافه کنیم. در مرحله‌ی بعد پس از اضافه شدن موفقیت‌آمیز رویداد به دیتابیس شبیه‌سازی شده، همان رویداد را با status code شماره‌ی 201 به کاربر برمی‌گردانیم:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

type event struct {
	ID          string `json:"ID"`
	Title       string `json:"Title"`
	Description string `json:"Description"`
}

type allEvents []event

var events = allEvents{
	{
		ID:          "1",
		Title:       "Introduction to Golang",
		Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
	},
}

func createEvent(w http.ResponseWriter, r *http.Request) {
	var newEvent event
	// Convert r.Body into a readable formart
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "Kindly enter data with the event id, title and description only in order to update")
	}

	json.Unmarshal(reqBody, &newEvent)

	// Add the newly created event to the array of events
	events = append(events, newEvent)

	// Return the 201 created status code
	w.WriteHeader(http.StatusCreated)
	// Return the newly created event
	json.NewEncoder(w).Encode(newEvent)
}

func homeLink(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome home!")
}

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", homeLink)
	router.HandleFunc("/event", createEvent).Methods("POST")
	log.Fatal(http.ListenAndServe(":8080", router))
}
اضافه کردن یک رویداد جدید به برنامه

نمایش تمام رویداد‌ها

برای نمایش تمام رویداد‌ها باید داده‌‌های events را نمایش دهیم:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

type event struct {
	ID          string `json:"ID"`
	Title       string `json:"Title"`
	Description string `json:"Description"`
}

type allEvents []event

var events = allEvents{
	{
		ID:          "1",
		Title:       "Introduction to Golang",
		Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
	},
}

func homeLink(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome home!")
}

func createEvent(w http.ResponseWriter, r *http.Request) {
	var newEvent event
	// Convert r.Body into a readable formart
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "Kindly enter data with the event id, title and description only in order to update")
	}

	json.Unmarshal(reqBody, &newEvent)

	// Add the newly created event to the array of events
	events = append(events, newEvent)

	// Return the 201 created status code
	w.WriteHeader(http.StatusCreated)
	// Return the newly created event
	json.NewEncoder(w).Encode(newEvent)
}

func getAllEvents(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(events)
}

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", homeLink)
	router.HandleFunc("/event", createEvent).Methods("POST")
	router.HandleFunc("/events", getAllEvents).Methods("GET")
	log.Fatal(http.ListenAndServe(":8080", router))
}
دریافت تمام رویدادها از سرور

نمایش یک رویداد خاص

برای نمایش یک رویداد خاص باید باید مقدار ID را به‌کمک Mux دریافت کنیم و درنهایت اگر ID ارسال شده با رویدادهای موجود در دیتابیس شبیه‌سازی شده همخوانی داشت، تمام داده‌های آن رویداد را نمایش دهیم:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

type event struct {
	ID          string `json:"ID"`
	Title       string `json:"Title"`
	Description string `json:"Description"`
}

type allEvents []event

var events = allEvents{
	{
		ID:          "1",
		Title:       "Introduction to Golang",
		Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
	},
}

func homeLink(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome home!")
}

func createEvent(w http.ResponseWriter, r *http.Request) {
	var newEvent event
	// Convert r.Body into a readable formart
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "Kindly enter data with the event id, title and description only in order to update")
	}

	json.Unmarshal(reqBody, &newEvent)

	// Add the newly created event to the array of events
	events = append(events, newEvent)

	// Return the 201 created status code
	w.WriteHeader(http.StatusCreated)
	// Return the newly created event
	json.NewEncoder(w).Encode(newEvent)
}

func getOneEvent(w http.ResponseWriter, r *http.Request) {
	// Get the ID from the url
	eventID := mux.Vars(r)["id"]

	// Get the details from an existing event
	// Use the blank identifier to avoid creating a value that will not be used
	for _, singleEvent := range events {
		if singleEvent.ID == eventID {
			json.NewEncoder(w).Encode(singleEvent)
		}
	}
}

func getAllEvents(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(events)
}

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", homeLink)
	router.HandleFunc("/event", createEvent).Methods("POST")
	router.HandleFunc("/events", getAllEvents).Methods("GET")
	router.HandleFunc("/events/{id}", getOneEvent).Methods("GET")
	log.Fatal(http.ListenAndServe(":8080", router))
}
دریافت یک رویداد خاص از سرور

به‌روزرسانی یک رویداد

برای به‌روزرسانی یک رویداد دقیقا شبیه بخش قبل عمل می‌کنیم و از Mux برای دریافت ID استفاده خواهیم کرد. اگر رویدادی با این ID وجود داشته باشد، به‌سراغ مقادیر ارسال شده در بدنه‌ی درخواست PATCH می‌رویم و آن‌ها را با مقادیر فعلی رویداد جایگزین می‌کنیم. درنهایت رویداد به‌روزرسانی شده را نمایش می‌دهیم:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

type event struct {
	ID          string `json:"ID"`
	Title       string `json:"Title"`
	Description string `json:"Description"`
}

type allEvents []event

var events = allEvents{
	{
		ID:          "1",
		Title:       "Introduction to Golang",
		Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
	},
}

func homeLink(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome home!")
}

func createEvent(w http.ResponseWriter, r *http.Request) {
	var newEvent event
	// Convert r.Body into a readable formart
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "Kindly enter data with the event id, title and description only in order to update")
	}

	json.Unmarshal(reqBody, &newEvent)

	// Add the newly created event to the array of events
	events = append(events, newEvent)

	// Return the 201 created status code
	w.WriteHeader(http.StatusCreated)
	// Return the newly created event
	json.NewEncoder(w).Encode(newEvent)
}

func getOneEvent(w http.ResponseWriter, r *http.Request) {
	// Get the ID from the url
	eventID := mux.Vars(r)["id"]

	// Get the details from an existing event
	// Use the blank identifier to avoid creating a value that will not be used
	for _, singleEvent := range events {
		if singleEvent.ID == eventID {
			json.NewEncoder(w).Encode(singleEvent)
		}
	}
}

func getAllEvents(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(events)
}

func updateEvent(w http.ResponseWriter, r *http.Request) {
	// Get the ID from the url
	eventID := mux.Vars(r)["id"]
	var updatedEvent event
	// Convert r.Body into a readable formart
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "Kindly enter data with the event title and description only in order to update")
	}

	json.Unmarshal(reqBody, &updatedEvent)

	for i, singleEvent := range events {
		if singleEvent.ID == eventID {
			singleEvent.Title = updatedEvent.Title
			singleEvent.Description = updatedEvent.Description
			events[i] = singleEvent
			json.NewEncoder(w).Encode(singleEvent)
		}
	}
}

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", homeLink)
	router.HandleFunc("/event", createEvent).Methods("POST")
	router.HandleFunc("/events", getAllEvents).Methods("GET")
	router.HandleFunc("/events/{id}", getOneEvent).Methods("GET")
	router.HandleFunc("/events/{id}", updateEvent).Methods("PATCH")
	log.Fatal(http.ListenAndServe(":8080", router))
}
به‌روزرسانی یک رویداد

حذف یک رویداد

حذف یک رویداد با متد DELETE انجام می‌شود بنابراین مقدار ID ارسال شده در درخواست‌هایی با متد DELETE را به کمک Mux دریافت خواهیم کرد و آن را با دیتابیس شبیه‌سازی شده مقایسه می‌کنیم. اگر در دیتابیس ما رویدادی با این ID وجود داشته باشد آن را حذف خواهیم کرد و درنهایت موفقیت‌آمیز بودن حذف رویداد را نمایش می‌دهیم:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

type event struct {
	ID          string `json:"ID"`
	Title       string `json:"Title"`
	Description string `json:"Description"`
}

type allEvents []event

var events = allEvents{
	{
		ID:          "1",
		Title:       "Introduction to Golang",
		Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
	},
}

func homeLink(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome home!")
}

func createEvent(w http.ResponseWriter, r *http.Request) {
	var newEvent event
	// Convert r.Body into a readable formart
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "Kindly enter data with the event id, title and description only in order to update")
	}

	json.Unmarshal(reqBody, &newEvent)

	// Add the newly created event to the array of events
	events = append(events, newEvent)

	// Return the 201 created status code
	w.WriteHeader(http.StatusCreated)
	// Return the newly created event
	json.NewEncoder(w).Encode(newEvent)
}

func getOneEvent(w http.ResponseWriter, r *http.Request) {
	// Get the ID from the url
	eventID := mux.Vars(r)["id"]

	// Get the details from an existing event
	// Use the blank identifier to avoid creating a value that will not be used
	for _, singleEvent := range events {
		if singleEvent.ID == eventID {
			json.NewEncoder(w).Encode(singleEvent)
		}
	}
}

func getAllEvents(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(events)
}

func updateEvent(w http.ResponseWriter, r *http.Request) {
	// Get the ID from the url
	eventID := mux.Vars(r)["id"]
	var updatedEvent event
	// Convert r.Body into a readable formart
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "Kindly enter data with the event title and description only in order to update")
	}

	json.Unmarshal(reqBody, &updatedEvent)

	for i, singleEvent := range events {
		if singleEvent.ID == eventID {
			singleEvent.Title = updatedEvent.Title
			singleEvent.Description = updatedEvent.Description
			events[i] = singleEvent
			json.NewEncoder(w).Encode(singleEvent)
		}
	}
}

func deleteEvent(w http.ResponseWriter, r *http.Request) {
	// Get the ID from the url
	eventID := mux.Vars(r)["id"]

	// Get the details from an existing event
	// Use the blank identifier to avoid creating a value that will not be used
	for i, singleEvent := range events {
		if singleEvent.ID == eventID {
			events = append(events[:i], events[i+1:]...)
			fmt.Fprintf(w, "The event with ID %v has been deleted successfully", eventID)
		}
	}
}

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", homeLink)
	router.HandleFunc("/event", createEvent).Methods("POST")
	router.HandleFunc("/events", getAllEvents).Methods("GET")
	router.HandleFunc("/events/{id}", getOneEvent).Methods("GET")
	router.HandleFunc("/events/{id}", updateEvent).Methods("PATCH")
	router.HandleFunc("/events/{id}", deleteEvent).Methods("DELETE")
	log.Fatal(http.ListenAndServe(":8080", router))
}
حذف یک رویداد

منبع: https://medium.com/the-andela-way/build-a-restful-json-api-with-golang-85a83420c9da