آموزش ساخت 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

همچنین بخوانید: معرفی هاست رایگان Golang

برچسب‌ها:

خدمات رایگان لیارا

۲.۵ گیگابایت فضای ذخیره‌سازی ابری رایگان

۲.۵ گیگابایت Object Storage سازگار با پروتکل S3 با دیسک‌های SSD به‌صورت رایگان دریافت کنید.

هاست رایگان برای دیتابیس‌

دیتابیس‌های MariaDB، PostgreSQL و Redis را فقط با یک کلیک و به‌صورت رایگان تهیه کنید.

سرویس DNS رایگان

به سادگی دامنه‌تان را اضافه کنید و به صورت رایگان رکورد‌های آن را مدیریت کنید.

۱۰۰ هزار تومان اعتبار اولیه

بعد از ثبت نام در لیارا مبلغ ۱۰۰ هزار تومان اعتبار هدیه دریافت می‌کنید که با توجه به ساعتی بودن هزینه سرویس‌ها، می‌توانید تمامی خدمات پولی را برای چندین هفته رایگان استفاده کنید.

ارسال ۱۰۰ ایمیل تراکنشی رایگان در هر ماه

در سرویس ایمیل لیارا شما می‌توانید تا ۱۰۰ ایمیل رایگان در هر ماه ارسال کنید و فقط برای بیش از آن هزینه پرداخت کنید. (به‌همراه دسترسی SMTP)

هاست رایگان برای انواع وبسایت

تفاوتی ندارد برای وبسایت خود از Node استفاده می‌کنید یا Laravel و Django، در لیارا می‌توانید به صورت کاملا رایگان آن را میزبانی کنید.

توسعه‌دهندگان درباره‌ی ما چه می‌گویند

تجربه کار باliara_cloud@امروز خیلی خوب بود. یکی از سرویس هام رو منتقل کردم روش و راضیم. انقد سریع و جذاب کارم راه افتادم اصن باورم نمیشد! برعکس سرویس های PaaS دیگه با اون همه پیچیدگیشون. دمتون گرم
...

MohammadReza
liara testimonial
keikaavousi

بعد از بسته شدن @fandoghpaas و ناراحتی همه‌مون از اینکه یه سرویس خوب و صادق نمی‌تونه از پس هزینه‌ها بر بیاد، سرویسم رو منتقل کردم به پاس لیارا (https://liara.ir @liara_cloud) . تجربه راحت و خوب. تفاوت‌هایی داشت که کمی کار می‌خواست ولی تا الان کاملا راضی.

jadi
liara testimonial
jadi

یه خسته نباشید باید به تصمیمliara_cloud@بگم،
بعد از چندین روز سرکله زدن با سرویس های مشابه بالاخره تصمیم گرفتم لیارا رو امتحان کنم و باور نمیشه ۱۰ دقیقه بیشتر وقت نبرد،
دمتون گرم.

Arch
liara testimonial
EbadiDev

واسه سرویس PaaS با اختلاف لیارا بهترین رابط کاربری داره و یکی از مزیت‌های سرویس دیتابیس‌شون اینه که خودشون به صورت دوره‌ای بکآپ میگیرن.
...

Ali Najafi
liara testimonial
me_ali_najafi

یکی از کارهای خوبی که جدیداً میکنم اینه که یه دیتابیس روی لیارا میسازم و به پروژه وصل میکنم اینطوری هم خونه و هم محل کار دیتابیس بروز رو دارم و راحت میتونم ادامه بدم کار روliara_cloud@

Navid
liara testimonial
1navid

عاشقliara_cloud@شدم درسته در حد AWS نیست ولی خب تجربه خوبی واسه پروژه های داخل ایران ارائه میده، میتونم رو CD هم اجراش کنم

Amir H Shekari
liara testimonial
vanenshi

همراه شما هستیم

در خصوص سفارش یا استفاده از سرویس‌ها سوالی دارید؟
تلفن واحد فروش:
۰۲۵-۳۳۵۵۷۶۱۹ (روزهای کاری ۹ الی ۱۷)
تلفن واحد فروش: ۳۳۵۵۷۶۱۹-۰۲۵ (روزهای کاری ۹ الی ۱۷)