آموزش ساخت 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 به شکل زیر است.
شبیهسازی دیتابیس
برای شبیهسازی دیتابیس میتوانیم رویدادها را به شکل زیر در برنامه تعریف کنیم:
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
همچنین بخوانید: معرفی هاست رایگان Golang
توسعهدهندگان دربارهی ما چه میگویند
تجربه کار باliara_cloud@امروز خیلی خوب بود. یکی از سرویس هام رو منتقل کردم روش و راضیم. انقد سریع و جذاب کارم راه افتادم اصن باورم نمیشد! برعکس سرویس های PaaS دیگه با اون همه پیچیدگیشون. دمتون گرم
...
MohammadReza
keikaavousi
بعد از بسته شدن @fandoghpaas و ناراحتی همهمون از اینکه یه سرویس خوب و صادق نمیتونه از پس هزینهها بر بیاد، سرویسم رو منتقل کردم به پاس لیارا (https://liara.ir @liara_cloud) . تجربه راحت و خوب. تفاوتهایی داشت که کمی کار میخواست ولی تا الان کاملا راضی.
jadi
jadi
یه خسته نباشید باید به تصمیمliara_cloud@بگم،
بعد از چندین روز سرکله زدن با سرویس های مشابه بالاخره تصمیم گرفتم لیارا رو امتحان کنم و باور نمیشه ۱۰ دقیقه بیشتر وقت نبرد،
دمتون گرم.
Arch
EbadiDev
واسه سرویس PaaS با اختلاف لیارا بهترین رابط کاربری داره و یکی از مزیتهای سرویس دیتابیسشون اینه که خودشون به صورت دورهای بکآپ میگیرن.
...
Ali Najafi
me_ali_najafi
یکی از کارهای خوبی که جدیداً میکنم اینه که یه دیتابیس روی لیارا میسازم و به پروژه وصل میکنم اینطوری هم خونه و هم محل کار دیتابیس بروز رو دارم و راحت میتونم ادامه بدم کار روliara_cloud@
Navid
1navid
عاشقliara_cloud@شدم درسته در حد AWS نیست ولی خب تجربه خوبی واسه پروژه های داخل ایران ارائه میده، میتونم رو CD هم اجراش کنم
Amir H Shekari
vanenshi