An example of a CRUD api in Go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

370 lines
8.9 KiB

package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
type RandomData struct {
// unexported for json
ID int64
DateCreated time.Time
LastUpdated time.Time
// exported for json
Title string `json:"title"`
Text string `json:"text"`
}
type randomDataHandler struct {
dbFilepath string
}
func InitLogs() {
var logsDir string = filepath.Join(".", "logs")
err := os.MkdirAll(logsDir, os.ModePerm)
if err != nil {
panic(err)
}
logfile, err := os.Create(filepath.Join(logsDir, "logs.log"))
if err != nil {
panic(err)
}
log.SetOutput(logfile)
}
func homepage(w http.ResponseWriter, r *http.Request) {
helpMessage := `
<h1>REST api in Go's standart library</h1>
<ul>
<li> (GET) <a href="/randomdata">/randomdata</a> - to get all database </li>
<li> (GET) /randomdata/{id} - to get specific random data under corresponding id </li>
<li> (POST) /randomdata - to create random data</li>
<li> (DELETE) /randomdata/{id} - to delete specified random data </li>
<li> (PUT) /randomdata/{id} - to update random data with given id </li>
</ul>
`
fmt.Fprint(w, helpMessage)
}
func newDatabaseHandler() *randomDataHandler {
dbDirpath := filepath.Join(".", "database")
err := os.MkdirAll(dbDirpath, os.ModePerm)
if err != nil {
panic(err)
}
dbFilepath := filepath.Join(dbDirpath, "database.json")
dbFile, err := os.OpenFile(dbFilepath, os.O_CREATE, os.ModePerm)
if err != nil {
panic(err)
}
defer dbFile.Close()
log.Println("Successfully created new database handler")
return &randomDataHandler{
dbFilepath: dbFilepath,
}
}
func (dbHandler *randomDataHandler) writeRandomData(newData RandomData) error {
dbBytes, err := dbHandler.readDatabase()
if err != nil {
log.Println("Error reading db (writeRandomData) : ", err)
}
var db []RandomData
err = json.Unmarshal(dbBytes, &db)
if err != nil {
log.Println("Error unmarshalling db (writeRandomData) : ", err)
}
db = append(db, newData)
dbFile, err := os.OpenFile(dbHandler.dbFilepath, os.O_WRONLY, 0644)
if err != nil {
log.Println("Error opening db file (writeRandomData) : ", err)
}
defer dbFile.Close()
jsonBytes, err := json.MarshalIndent(db, "", " ")
if err != nil {
log.Println("Error marshalling db (writeRandomData) : ", err)
}
dbFile.Write(jsonBytes)
return nil
}
func (dbHandler *randomDataHandler) readDatabase() ([]byte, error) {
dbBytes, err := os.ReadFile(dbHandler.dbFilepath)
if err != nil {
log.Println("Error reading db (readDatabase) : ", err)
return nil, err
}
return dbBytes, nil
}
func (dbHandler *randomDataHandler) removeRandomData(id int64) error {
dbBytes, err := dbHandler.readDatabase()
if err != nil {
return err
}
var db []RandomData
err = json.Unmarshal(dbBytes, &db)
if err != nil {
return err
}
var counter int64 = 0
for _, randomData := range db {
if id == randomData.ID {
db = append(db[:counter], db[counter+1:]...)
err = dbHandler.writeDB(db)
if err != nil {
return err
}
}
counter++
}
return nil
}
func (dbHandler *randomDataHandler) writeDB(db []RandomData) error {
jsonEncodedDB, err := json.MarshalIndent(db, "", " ")
if err != nil {
return err
}
dbFile, err := os.OpenFile(dbHandler.dbFilepath, os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer dbFile.Close()
dbFile.Write(jsonEncodedDB)
return nil
}
func (dbHandler *randomDataHandler) get(w http.ResponseWriter, r *http.Request) {
dbBytes, err := dbHandler.readDatabase()
if err != nil {
log.Println("Error reading db (get) : ", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(dbBytes)
}
func (dbHandler *randomDataHandler) create(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("content-type") != "application/json" {
w.WriteHeader(http.StatusUnsupportedMediaType)
w.Write([]byte(fmt.Sprintf("Got `%s` instead of `application/json`", r.Header.Get("content-type"))))
return
}
requestBody, err := io.ReadAll(r.Body)
if err != nil {
log.Println("Error reading http request (create) : ", err)
w.WriteHeader(http.StatusBadRequest)
return
}
defer r.Body.Close()
var newRandomData RandomData
err = json.Unmarshal(requestBody, &newRandomData)
if err != nil {
log.Printf("Error unmarshalling http request (create) : %q \n", err)
w.WriteHeader(http.StatusBadRequest)
return
}
newRandomData.DateCreated = time.Now().UTC()
newRandomData.LastUpdated = newRandomData.DateCreated
newRandomData.ID = time.Now().UTC().UnixNano()
err = dbHandler.writeRandomData(newRandomData)
if err != nil {
log.Println("Error writing RandomData (create): ", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
log.Println("Successfuly added to db : ", newRandomData)
}
func (dbHandler *randomDataHandler) getSpecificRandomData(w http.ResponseWriter, r *http.Request) {
givenID := strings.Split(r.URL.String(), "/")[2]
dbBytes, err := dbHandler.readDatabase()
if err != nil {
log.Println("Error reading db (getSpecificRandomData) : ", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var db []RandomData
err = json.Unmarshal(dbBytes, &db)
if err != nil {
log.Println("Error unmarshalling database (getSpecificRandomData) : ", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
for _, randomData := range db {
if int64GivenID == randomData.ID {
response, err := json.MarshalIndent(randomData, "", " ")
if err != nil {
log.Println("Error marshaling response(getSpecificRandomData) : ", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(response)
return
}
}
w.WriteHeader(http.StatusNotFound)
}
func (dbHandler *randomDataHandler) updateSpecificRandomData(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("content-type") != "application/json" {
w.WriteHeader(http.StatusUnsupportedMediaType)
w.Write([]byte(fmt.Sprintf("Got `%s` instead of `application/json`", r.Header.Get("content-type"))))
return
}
requestBody, err := io.ReadAll(r.Body)
if err != nil {
log.Println("Error reading http request (create) : ", err)
w.WriteHeader(http.StatusBadRequest)
r.Body.Close()
return
}
defer r.Body.Close()
var givenUpdatedRandomData RandomData
err = json.Unmarshal(requestBody, &givenUpdatedRandomData)
if err != nil {
log.Println("Error unmarshalling request body (updateSpecificRandomData) : ", err)
return
}
givenID := strings.Split(r.URL.String(), "/")[2]
dbBytes, err := dbHandler.readDatabase()
if err != nil {
log.Println("Error reading db (updateSpecificRandomData) : ", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var db []RandomData
err = json.Unmarshal(dbBytes, &db)
if err != nil {
log.Println("Error unmarshalling database (update) : ", err)
return
}
int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
var counter int64
for _, randomData := range db {
if int64GivenID == randomData.ID {
var updatedRandomData RandomData
updatedRandomData = givenUpdatedRandomData
updatedRandomData.ID = randomData.ID
updatedRandomData.DateCreated = randomData.DateCreated
updatedRandomData.LastUpdated = time.Now().UTC()
dbHandler.removeRandomData(int64GivenID)
dbHandler.writeRandomData(updatedRandomData)
log.Printf("Successfully updated RandomData with id %v \n", updatedRandomData.ID)
return
}
counter++
}
w.WriteHeader(http.StatusNotFound)
}
func (dbHandler *randomDataHandler) deleteSpecificRandomData(w http.ResponseWriter, r *http.Request) {
givenID := strings.Split(r.URL.String(), "/")[2]
int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
err := dbHandler.removeRandomData(int64GivenID)
if err != nil {
log.Println("Error removing RandomData (deleteSpecificRandomData) : ", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
log.Printf("Successfully deleted RandomData with id %v \n", int64GivenID)
}
func (dbHandler *randomDataHandler) handle(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
dbHandler.get(w, r)
case "POST":
dbHandler.create(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (dbHandler *randomDataHandler) handleSpecific(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
dbHandler.getSpecificRandomData(w, r)
case "PUT":
dbHandler.updateSpecificRandomData(w, r)
case "DELETE":
dbHandler.deleteSpecificRandomData(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func init() {
InitLogs()
}
func main() {
databaseHandler := newDatabaseHandler()
servemux := http.NewServeMux()
servemux.HandleFunc("/", homepage)
servemux.HandleFunc("/randomdata", databaseHandler.handle)
servemux.HandleFunc("/randomdata/", databaseHandler.handleSpecific)
server := &http.Server{
Addr: ":8000",
Handler: servemux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
log.Fatal(server.ListenAndServe())
}