Skip to content

Commit 05967e2

Browse files
committed
Gopher Api created for respective article on the blog
0 parents  commit 05967e2

File tree

8 files changed

+273
-0
lines changed

8 files changed

+273
-0
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Gopher API
2+
The Gopher API, is a simple CRUD API for formative purpose, we're building it while writing the posts of the [blog](https://blog.friendsofgo.tech).
3+
4+
## How can I use it?
5+
6+
**Install**
7+
8+
```sh
9+
$ go get -u github.com/friendsofgo/gopherapi/cmd/gopherapi
10+
```
11+
12+
**Usage**
13+
Launch server with predefined data
14+
15+
```sh
16+
$ gopherapi --withData
17+
The gopher server is on tap now: http://localhost:8080
18+
```
19+
20+
## Endpoints
21+
22+
Fetch all gophers
23+
24+
```
25+
GET /gophers
26+
```
27+
28+
Fetch a gopher by ID
29+
30+
```
31+
GET /gophers/{gopher_id}
32+
```
33+
34+
## Contributing
35+
If you think that you can improve with new endpoints, and functionallities the API feel free to contribute with this project with fork this repo and send your Pull Request.
36+
37+
## License
38+
MIT License, see [LICENSE](https://github.com/friendsofgo/gopherapi/blob/master/LICENSE)
39+

cmd/gopherapi/main.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
9+
"github.com/friendsofgo/gopher-api/pkg/storage/inmem"
10+
11+
sample "github.com/friendsofgo/gopher-api/cmd/sample-data"
12+
gopher "github.com/friendsofgo/gopher-api/pkg"
13+
14+
"github.com/friendsofgo/gopher-api/pkg/server"
15+
)
16+
17+
func main() {
18+
withData := flag.Bool("withData", false, "initialize the api with some gophers")
19+
flag.Parse()
20+
21+
var gophers map[string]*gopher.Gopher
22+
if *withData {
23+
gophers = sample.Gophers
24+
}
25+
26+
repo := inmem.NewGopherRepository(gophers)
27+
s := server.New(repo)
28+
29+
fmt.Println("The gopher server is on tap now: http://localhost:8080")
30+
log.Fatal(http.ListenAndServe(":8080", s.Router()))
31+
}

cmd/sample-data/data.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package sample
2+
3+
import (
4+
gopher "github.com/friendsofgo/gopher-api/pkg"
5+
)
6+
7+
var Gophers = map[string]*gopher.Gopher{
8+
"01D3XZ3ZHCP3KG9VT4FGAD8KDR": &gopher.Gopher{
9+
ID: "01D3XZ3ZHCP3KG9VT4FGAD8KDR",
10+
Name: "Jenny",
11+
Age: 18,
12+
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/0ceb2c10fc0c30575c18ff1defa1ffd41501bc62.png",
13+
},
14+
"01D3XZ7CN92AKS9HAPSZ4D5DP9": &gopher.Gopher{
15+
ID: "01D3XZ7CN92AKS9HAPSZ4D5DP9",
16+
Name: "Billy",
17+
Age: 24,
18+
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/13c7d425111a501600db8587b52bb292836c5bee.png",
19+
},
20+
"01D3XZ89NFJZ9QT2DHVD462AC2": &gopher.Gopher{
21+
ID: "01D3XZ89NFJZ9QT2DHVD462AC2",
22+
Name: "Rainbow",
23+
Age: 48,
24+
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/b9e8d637c91c089fd56d7b159825fc9089377118.png",
25+
},
26+
"01D3XZ8JXHTDA6XY05EVJVE9Z2": &gopher.Gopher{
27+
ID: "01D3XZ8JXHTDA6XY05EVJVE9Z2",
28+
Name: "Bjorn",
29+
Age: 32,
30+
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/fd01b36091560c2a128b8fddfb2c627d8bb7417c.png",
31+
},
32+
}

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module github.com/friendsofgo/gopher-api
2+
3+
require (
4+
github.com/aperezg/monster v0.0.0-20190201085025-648ece19f831
5+
github.com/gorilla/mux v1.7.0
6+
)

go.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
github.com/aperezg/monster v0.0.0-20190201085025-648ece19f831 h1:zcVrTQ22nmJ1+0sp9Lc7OuAMkNqqbEy/3OKXiWB2ods=
2+
github.com/aperezg/monster v0.0.0-20190201085025-648ece19f831/go.mod h1:59SPFI3k23yeORvJqB1py+ZSdAgQKVaPAMZ0ZUWvZyo=
3+
github.com/google/jsonapi v0.0.0-20181016150055-d0428f63eb51/go.mod h1:XSx4m2SziAqk9DXY9nz659easTq4q6TyrpYd9tHSm0g=
4+
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
5+
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
6+
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
7+
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=

pkg/gopher.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package gopher
2+
3+
// Gopher defines the properties of a gopher to be listed
4+
type Gopher struct {
5+
ID string `json:"ID"`
6+
Name string `json:"name,omitempty"`
7+
Image string `json:"image,omitempty"`
8+
Age int `json:"age,omitempty"`
9+
}
10+
11+
// Repository provides access to the gopher storage
12+
type GopherRepository interface {
13+
// CreateGopher saves a given gopher
14+
CreateGopher(g *Gopher) error
15+
// FetchGophers return all gophers saved in storage
16+
FetchGophers() ([]*Gopher, error)
17+
// DeleteGopher remove gopher with given ID
18+
DeleteGopher(ID string) error
19+
// UpdateGopher modify gopher with given ID and given new data
20+
UpdateGopher(ID string, g *Gopher) error
21+
// FetchGopherByID returns the gopher with given ID
22+
FetchGopherByID(ID string) (*Gopher, error)
23+
}

pkg/server/api.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package server
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
gopher "github.com/friendsofgo/gopher-api/pkg"
8+
9+
"github.com/gorilla/mux"
10+
)
11+
12+
type api struct {
13+
router http.Handler
14+
repository gopher.GopherRepository
15+
}
16+
17+
type Server interface {
18+
Router() http.Handler
19+
}
20+
21+
func New(repo gopher.GopherRepository) Server {
22+
a := &api{repository: repo}
23+
24+
r := mux.NewRouter()
25+
r.HandleFunc("/gophers", a.fetchGophers).Methods(http.MethodGet)
26+
r.HandleFunc("/gophers/{ID:[a-zA-Z0-9_]+}", a.fetchGopher).Methods(http.MethodGet)
27+
28+
a.router = r
29+
return a
30+
}
31+
32+
func (a *api) Router() http.Handler {
33+
return a.router
34+
}
35+
36+
func (a *api) fetchGophers(w http.ResponseWriter, r *http.Request) {
37+
gophers, _ := a.repository.FetchGophers()
38+
39+
w.Header().Set("Content-Type", "application/json")
40+
json.NewEncoder(w).Encode(gophers)
41+
}
42+
43+
func (a *api) fetchGopher(w http.ResponseWriter, r *http.Request) {
44+
vars := mux.Vars(r)
45+
gopher, err := a.repository.FetchGopherByID(vars["ID"])
46+
w.Header().Set("Content-Type", "application/json")
47+
if err != nil {
48+
w.WriteHeader(http.StatusNotFound) // We use not found for simplicity
49+
json.NewEncoder(w).Encode("Gopher Not found")
50+
return
51+
}
52+
53+
json.NewEncoder(w).Encode(gopher)
54+
}

pkg/storage/inmem/repository.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package inmem
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
gopher "github.com/friendsofgo/gopher-api/pkg"
8+
)
9+
10+
type gopherRepository struct {
11+
mtx sync.RWMutex
12+
gophers map[string]*gopher.Gopher
13+
}
14+
15+
func NewGopherRepository(gophers map[string]*gopher.Gopher) gopher.GopherRepository {
16+
if gophers == nil {
17+
gophers = make(map[string]*gopher.Gopher)
18+
}
19+
20+
return &gopherRepository{
21+
gophers: gophers,
22+
}
23+
}
24+
25+
func (r *gopherRepository) CreateGopher(g *gopher.Gopher) error {
26+
r.mtx.Lock()
27+
defer r.mtx.Unlock()
28+
if err := r.checkIfExists(g.ID); err != nil {
29+
return err
30+
}
31+
r.gophers[g.ID] = g
32+
return nil
33+
}
34+
35+
func (r *gopherRepository) FetchGophers() ([]*gopher.Gopher, error) {
36+
r.mtx.Lock()
37+
defer r.mtx.Unlock()
38+
values := make([]*gopher.Gopher, 0, len(r.gophers))
39+
for _, value := range r.gophers {
40+
values = append(values, value)
41+
}
42+
return values, nil
43+
}
44+
45+
func (r *gopherRepository) DeleteGopher(ID string) error {
46+
r.mtx.Lock()
47+
defer r.mtx.Unlock()
48+
delete(r.gophers, ID)
49+
50+
return nil
51+
}
52+
53+
func (r *gopherRepository) UpdateGopher(ID string, g *gopher.Gopher) error {
54+
r.mtx.Lock()
55+
defer r.mtx.Unlock()
56+
r.gophers[ID] = g
57+
return nil
58+
}
59+
60+
func (r *gopherRepository) FetchGopherByID(ID string) (*gopher.Gopher, error) {
61+
r.mtx.Lock()
62+
defer r.mtx.Unlock()
63+
64+
for _, v := range r.gophers {
65+
if v.ID == ID {
66+
return v, nil
67+
}
68+
}
69+
70+
return nil, fmt.Errorf("The ID %s doesn't exist", ID)
71+
}
72+
73+
func (r *gopherRepository) checkIfExists(ID string) error {
74+
for _, v := range r.gophers {
75+
if v.ID == ID {
76+
return fmt.Errorf("The gopher %s is already exist", ID)
77+
}
78+
}
79+
80+
return nil
81+
}

0 commit comments

Comments
 (0)