چطور با زبان GO یک RESTful API بسازیم

پیش‌نیازهافرض ما بر این است که شما از قبل با زبان Go آشنا شده‌اید و در حال حاضر این زبان برنامه‌نویسی بر روی سیستم شما نصب است.پس از نصب زبان Go باید در GOPATH که اکثر افراد تمام پروژه‌هایشان را در این مسیر نگهداری می‌کنند، پروژه‌ی فعلی را توسعه دهیم بنابراین در پوشه‌ی src می‌توانید یک فولدر خاص برای این پروژه ایجاد کنید یا برای ساماندهی بیشتر پروژه‌های مختلف یک پوشه با نام github.com ایجاد کنید و در آن پوشه‌ی دیگری ایجاد کنید و نام آن را همان نام کاربری خود در GitHub قرار دهید. در نهایت در مسیر GOPATH/src/github.com/<Github username> یک پوشه‌ی جدید برای پروژه‌ی فعلی با نام دلخواه ایجاد کنید.توسعه‌ی APIبرای پیاده‌سازی ساختار اولیه‌ی پروژه می‌توانید به شکل زیر عمل کنید:mkdir go-rest-api cd go-rest-api touch main.goپس از ایجاد کردن فایل main.go از یک ویرایشگر کد برای توسعه‌ی برنامه کمک می‌گیریم و کدهای زیر را در فایل ایجاد شده قرار می‌دهیم:package main import &quotfmt&quot func main() { fmt.Println(&quotHello World!&quot) }برای کامپایل این برنامه ساده می‌توانید در Terminal فعلی دستور go build را اجرا کرده و درنهایت به شکل زیر برنامه‌ی کامپایل شده را اجرا کنید:./go-rest-apiانتظار می‌رود که خروجی زیر در Terminal شما چاپ شود:Hello World!راه‌اندازی سرور HTTP با استفاده از Gorilla MuxGorilla Mux یک پکیج است که به شما امکان می‌دهد routeهای مورد نیاز برنامه را ایجاد کرده و هر کدام از درخواست‌های ورودی را به Controller مربوطه ارجاع دهید. قبل از کار با پکیج Mux باید آن را به شکل زیر نصب کرده:go mod init go get -u github.com/gorilla/muxو در مرحله‌ی بعد اولین endpoint برنامه را تعریف کنیم:package main import ( &quotfmt&quot &quotlog&quot &quotnet/http&quot &quotgithub.com/gorilla/mux&quot ) func homeLink(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, &quotWelcome home!&quot) } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc(&quot/&quot, homeLink) log.Fatal(http.ListenAndServe(&quot:8080&quot, router)) }مطمئنا پس از ایجاد هر تغییری در کدها باید برنامه را مجددا اجرا کنید. حال برای اجرای برنامه دو راه حل وجود دارد که یکی از آن‌ها کامپایل برنامه و اجرای برنامه‌ی کامپایل شده است اما برای سریع‌تر شدن فرایند تست برنامه می‌توانید دستور go run main.go را اجرا کنید که بدون نیاز به کامپایل، سرور توسعه داده شده را برای شما اجرا می‌کند.خروجی مورد انتظار پس از راه‌اندازی و ارسال درخواست GET به برنامه در آدرس localhost:8080 به شکل زیر است.شبیه‌سازی دیتابیسبرای شبیه‌سازی دیتابیس می‌توانیم رویداد‌ها را به شکل زیر در برنامه تعریف کنیم:type event struct { ID string `json:&quotID&quot` Title string `json:&quotTitle&quot` Description string `json:&quotDescription&quot` } type allEvents []event var events = allEvents{ { ID: &quot1&quot, Title: &quotIntroduction to Golang&quot, Description: &quotCome join us for a chance to learn how golang works and get to eventually try it out&quot, }, }در این ساختار فقط از ID، Title و Description برای ذخیره‌ی رویدادها استفاده کرده‌ایم و در ادامه کدهای مربوط به اضافه کردن، به‌روزرسانی و حذف رویدادها را پیاده‌سازی خواهیم کرد.اضافه کردن یک رویداد جدیدبرای ایجاد یک رویداد جدید باید داده‌های ارسال شده به‌صورت POST را دریافت کرده و به دیتابیس شبیه‌سازی شده اضافه کنیم. در مرحله‌ی بعد پس از اضافه شدن موفقیت‌آمیز رویداد به دیتابیس شبیه‌سازی شده، همان رویداد را با status code شماره‌ی 201 به کاربر برمی‌گردانیم:package main import ( &quotencoding/json&quot &quotfmt&quot &quotio/ioutil&quot &quotlog&quot &quotnet/http&quot &quotgithub.com/gorilla/mux&quot ) type event struct { ID string `json:&quotID&quot` Title string `json:&quotTitle&quot` Description string `json:&quotDescription&quot` } type allEvents []event var events = allEvents{ { ID: &quot1&quot, Title: &quotIntroduction to Golang&quot, Description: &quotCome join us for a chance to learn how golang works and get to eventually try it out&quot, }, } 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, &quotKindly enter data with the event id, title and description only in order to update&quot) } 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, &quotWelcome home!&quot) } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc(&quot/&quot, homeLink) router.HandleFunc(&quot/event&quot, createEvent).Methods(&quotPOST&quot) log.Fatal(http.ListenAndServe(&quot:8080&quot, router)) }نمایش تمام رویداد‌هابرای نمایش تمام رویداد‌ها باید داده‌‌های events را نمایش دهیم:package main import ( &quotencoding/json&quot &quotfmt&quot &quotio/ioutil&quot &quotlog&quot &quotnet/http&quot &quotgithub.com/gorilla/mux&quot ) type event struct { ID string `json:&quotID&quot` Title string `json:&quotTitle&quot` Description string `json:&quotDescription&quot` } type allEvents []event var events = allEvents{ { ID: &quot1&quot, Title: &quotIntroduction to Golang&quot, Description: &quotCome join us for a chance to learn how golang works and get to eventually try it out&quot, }, } func homeLink(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, &quotWelcome home!&quot) } 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, &quotKindly enter data with the event id, title and description only in order to update&quot) } 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(&quot/&quot, homeLink) router.HandleFunc(&quot/event&quot, createEvent).Methods(&quotPOST&quot) router.HandleFunc(&quot/events&quot, getAllEvents).Methods(&quotGET&quot) log.Fatal(http.ListenAndServe(&quot:8080&quot, router)) }نمایش یک رویداد خاصبرای نمایش یک رویداد خاص باید باید مقدار ID را به‌کمک Mux دریافت کنیم و درنهایت اگر ID ارسال شده با رویدادهای موجود در دیتابیس شبیه‌سازی شده همخوانی داشت، تمام داده‌های آن رویداد را نمایش دهیم:package main import ( &quotencoding/json&quot &quotfmt&quot &quotio/ioutil&quot &quotlog&quot &quotnet/http&quot &quotgithub.com/gorilla/mux&quot ) type event struct { ID string `json:&quotID&quot` Title string `json:&quotTitle&quot` Description string `json:&quotDescription&quot` } type allEvents []event var events = allEvents{ { ID: &quot1&quot, Title: &quotIntroduction to Golang&quot, Description: &quotCome join us for a chance to learn how golang works and get to eventually try it out&quot, }, } func homeLink(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, &quotWelcome home!&quot) } 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, &quotKindly enter data with the event id, title and description only in order to update&quot) } 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)[&quotid&quot] // 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(&quot/&quot, homeLink) router.HandleFunc(&quot/event&quot, createEvent).Methods(&quotPOST&quot) router.HandleFunc(&quot/events&quot, getAllEvents).Methods(&quotGET&quot) router.HandleFunc(&quot/events/{id}&quot, getOneEvent).Methods(&quotGET&quot) log.Fatal(http.ListenAndServe(&quot:8080&quot, router)) }به‌روزرسانی یک رویدادبرای به‌روزرسانی یک رویداد دقیقا شبیه بخش قبل عمل می‌کنیم و از Mux برای دریافت ID استفاده خواهیم کرد. اگر رویدادی با این ID وجود داشته باشد، به‌سراغ مقادیر ارسال شده در بدنه‌ی درخواست PATCH می‌رویم و آن‌ها را با مقادیر فعلی رویداد جایگزین می‌کنیم. درنهایت رویداد به‌روزرسانی شده را نمایش می‌دهیم:package main import ( &quotencoding/json&quot &quotfmt&quot &quotio/ioutil&quot &quotlog&quot &quotnet/http&quot &quotgithub.com/gorilla/mux&quot ) type event struct { ID string `json:&quotID&quot` Title string `json:&quotTitle&quot` Description string `json:&quotDescription&quot` } type allEvents []event var events = allEvents{ { ID: &quot1&quot, Title: &quotIntroduction to Golang&quot, Description: &quotCome join us for a chance to learn how golang works and get to eventually try it out&quot, }, } func homeLink(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, &quotWelcome home!&quot) } 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, &quotKindly enter data with the event id, title and description only in order to update&quot) } 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)[&quotid&quot] // 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)[&quotid&quot] var updatedEvent event // Convert r.Body into a readable formart reqBody, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Fprintf(w, &quotKindly enter data with the event title and description only in order to update&quot) } 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(&quot/&quot, homeLink) router.HandleFunc(&quot/event&quot, createEvent).Methods(&quotPOST&quot) router.HandleFunc(&quot/events&quot, getAllEvents).Methods(&quotGET&quot) router.HandleFunc(&quot/events/{id}&quot, getOneEvent).Methods(&quotGET&quot) router.HandleFunc(&quot/events/{id}&quot, updateEvent).Methods(&quotPATCH&quot) log.Fatal(http.ListenAndServe(&quot:8080&quot, router)) }حذف یک رویدادحذف یک رویداد با متد DELETE انجام می‌شود بنابراین مقدار ID ارسال شده در درخواست‌هایی با متد DELETE را به کمک Mux دریافت خواهیم کرد و آن را با دیتابیس شبیه‌سازی شده مقایسه می‌کنیم. اگر در دیتابیس ما رویدادی با این ID وجود داشته باشد آن را حذف خواهیم کرد و درنهایت موفقیت‌آمیز بودن حذف رویداد را نمایش می‌دهیم:package main import ( &quotencoding/json&quot &quotfmt&quot &quotio/ioutil&quot &quotlog&quot &quotnet/http&quot &quotgithub.com/gorilla/mux&quot ) type event struct { ID string `json:&quotID&quot` Title string `json:&quotTitle&quot` Description string `json:&quotDescription&quot` } type allEvents []event var events = allEvents{ { ID: &quot1&quot, Title: &quotIntroduction to Golang&quot, Description: &quotCome join us for a chance to learn how golang works and get to eventually try it out&quot, }, } func homeLink(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, &quotWelcome home!&quot) } 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, &quotKindly enter data with the event id, title and description only in order to update&quot) } 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)[&quotid&quot] // 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)[&quotid&quot] var updatedEvent event // Convert r.Body into a readable formart reqBody, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Fprintf(w, &quotKindly enter data with the event title and description only in order to update&quot) } 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)[&quotid&quot] // 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, &quotThe event with ID %v has been deleted successfully&quot, eventID) } } } func main() { router := mux.NewRouter().StrictSlash(true) router.HandleFunc(&quot/&quot, homeLink) router.HandleFunc(&quot/event&quot, createEvent).Methods(&quotPOST&quot) router.HandleFunc(&quot/events&quot, getAllEvents).Methods(&quotGET&quot) router.HandleFunc(&quot/events/{id}&quot, getOneEvent).Methods(&quotGET&quot) router.HandleFunc(&quot/events/{id}&quot, updateEvent).Methods(&quotPATCH&quot) router.HandleFunc(&quot/events/{id}&quot, deleteEvent).Methods(&quotDELETE&quot) log.Fatal(http.ListenAndServe(&quot:8080&quot, router)) }