-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRestAPI_CRUD.go
More file actions
386 lines (340 loc) · 12.6 KB
/
RestAPI_CRUD.go
File metadata and controls
386 lines (340 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
package main
// import packages
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
// loading the driver anonymously,
// *aliasing its package qualifier to _
// *so none of its exported names are visible to our code.
_ "github.com/lib/pq"
)
// declare a struct of type User
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Sex string `json:"sex"`
Date_created string `json:"date_created"`
}
// declare and assign postgres db connection detail
// *Constants are declared like variables, but with the const keyword.
// *Constants can be character, string, boolean, or numeric values.
// *Constants cannot be declared using the := syntax.
const (
host = "localhost" // the host address
port = 5432 // the port
user = [db username]
password = [db password]
dbname = [db name]
)
// a function that sets up the database and establishes connection
func setupDB(user, password, host, dbname string) (*sql.DB, error) {
psqlInfo := fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable", host, user, password, dbname)
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return db, nil
}
// the main function that get executed first when the program is run. the start point of the app
func main() {
// creating the database object and opening connection
db, err := setupDB(user, password, host, dbname)
if err != nil {
panic(err)
}
// Defering the closing of the database connection to give room for further query
defer db.Close()
// executing an SQL query statement
_, err = db.Exec("DROP table if EXISTS users")
if err != nil {
panic(err)
}
// To check right away that the database is available and accessible
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("successfully pinged the db")
// an SQL query statement that creates 'users' table if not exists
createUserTable := `
CREATE table IF NOT EXISTS users (
id serial PRIMARY KEY,
username varchar NOT NULL,
email varchar NOT NULL,
firstname varchar,
lastname varchar,
sex varchar,
date_created timestamptz DEFAULT CURRENT_TIMESTAMP
);
`
// executing the SQL query statement
_, err = db.Exec(createUserTable)
if err != nil {
panic(err)
}
fmt.Println("users table created successfully!")
// inserting records into users table
// assigning the SQL query to insertUser variable
insertUser := `
INSERT into users (
username,
email,
firstname,
lastname,
sex
) VALUES
('bash', 'bash@gmail.com', 'Bashir', 'Anakobe', 'male'),
('teemah', 'teemah@gmail.com', 'Fatimah', 'Muhammed', 'female'),
('wasman', 'wasman@gmail.com', 'Abdulwasiu', 'Anakobe', 'male'),
('medo', 'medo@gmail.com', 'Ahmed', 'Ibrahim', 'male'),
('zain', 'zain@gmail.com', 'Zainab', 'Idris', 'female'),
('stacia', 'stacia@gmail.com', 'Anastasia', 'You', 'female')
;
`
// executing the query
_, err = db.Exec(insertUser)
if err != nil {
panic(err)
}
fmt.Println("users added successfully!")
// view records from the users table on the command line
// * This script uses the db.Query function to execute
// * a SELECT statement to retrieve all records from
// * the "users" table. The rows.Next function is then
// * used in a loop to iterate through the returned rows,
// * and the rows.Scan function is used to extract
// * the values for each column into variables.
// * You can also use sql.QueryRow if you expect only one row
// * to be returned by your query.
rows, err := db.Query("SELECT * FROM users")
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var id int
var username string
var email string
var firstname string
var lastname string
var sex string
var date_created string
// copying columns (fields) in the current row into the address destination
// * Scan copies the columns in the current row into the values pointed at by dest.
// * The number of values in dest must be the same as the number of columns in Rows.
// * Scan converts columns read from the database into the following common
// * Go types and special types provided by the sql package:
err = rows.Scan(&id, &username, &email, &firstname, &lastname, &sex, &date_created)
if err != nil {
panic(err)
}
fmt.Println("ID:", id, "Username:", username, "Email:", email, "Fisrtname:", firstname, "Lastname:", lastname, "Sex:", sex, "Date Created:", date_created)
}
err = rows.Err()
if err != nil {
panic(err)
}
// * http.HandleFunc et al apply your handlers to a package-global
// * instance of the ServeMux held in the http package,
// * which http.ListenAndServe then starts.
// http.HandleFunc("/users", handleViewUsers)
// http.HandleFunc("/newuser", handleCreateUser)
// fmt.Println("Server listening on port 3000...")
// http.ListenAndServe(":3000", nil)
// * You can also create your own instance which gives you some
// * more control and makes it easier to unit test.
// * ServeMux is an HTTP request multiplexer. It is used for request routing and dispatching.
// * The NewServeMux function allocates and returns a new ServeMux.
mux := http.NewServeMux()
mux.HandleFunc("/users", handleViewUsers)
mux.HandleFunc("/newuser", handleCreateUser)
mux.HandleFunc("/userupdate", handleUpdateUser)
mux.HandleFunc("/deleteuser", handleDeleteUser)
fmt.Println("Server listening on port 3000...")
http.ListenAndServe(":3000", mux)
} // end of func main()
// creating users slice to store user data
// * "users" variable becomes a slice of struct User
var users []User
// creating a handler function for users' GET request
func handleViewUsers(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
// connecting to database
db, err := setupDB(user, password, host, dbname)
if err != nil {
panic(err)
}
// Defering the closing of the database connection to give room for further query
defer db.Close()
// SELECT statement to retrieve all records from the "users" table
rows, err := db.Query("SELECT * FROM users")
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
defer rows.Close()
// Iterating through the returned rows and extracting the values for each column into variables
for rows.Next() {
var user User // * user is instance of Sruct User
// checking if error occurs while concurrently extracting data
// * "Scan the rows and assign the values to the variables in user struct,
// * if there is an error return an internal server error"
if err := rows.Scan(&user.ID, &user.Username, &user.Email, &user.Firstname, &user.Lastname, &user.Sex, &user.Date_created); err != nil {
// sending an HTTP error response with the appropriate status code and message,
// and then returning from the function to stop further execution
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
users = append(users, user)
}
// Encoding the users data to json and sending it as response
// * json.NewEncoder(w).Encode(users) is used to encode the users variable
// * as JSON and write it to the io.Writer type variable w. The json.NewEncoder(w)
// * creates a new encoder that writes to the w variable, and
// * the .Encode(users) method is called on that encoder to
// * write the JSON encoding of users to w. This is typically used to
// * write the JSON representation of a Go data structure
// * to an HTTP response body or to a file on disk.
json.NewEncoder(w).Encode(users)
// setting slice users to empty to avoid duplicate append on reload
users = nil
}
// creating a handler function for new user POST request
func handleCreateUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
// * API keys are a common way to authenticate and authorize access to your API.
// * One way to implement API keys in your Go application would be to
// * include a header in the HTTP request called "API-KEY" and check its value
// * in your request handler before processing the request.
// The following code checks for the presence of
// the "API-KEY" header and its value before processing further request
apiKey := r.Header.Get("API-KEY")
if apiKey != "your_api_key" {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
// Connecting to the database
db, err := setupDB(user, password, host, dbname)
if err != nil {
panic(err)
}
defer db.Close()
// Parsing the request body as json
var newUser User
if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Inserting the new user into the "users" table
_, err = db.Exec("INSERT INTO users (username, email, firstname, lastname, sex) VALUES ($1, $2, $3, $4, $5)", newUser.Username, newUser.Email, newUser.Firstname, newUser.Lastname, newUser.Sex)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
// Sending a response indicating success
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}
// *Note! You cannot set the request body r.Body directly in the
// *handleCreateUser handler to test. The r.Body is an io.ReadCloser
// *and is populated by the http server when it receives a request.
// *To test the handleCreateUser handler, you would need to simulate
// *an HTTP request to the server, and include the JSON payload in the
// *request body. This can be done using a testing framework like
// *net/http/httptest package in go or a HTTP client library
// *like net/http or github.com/golang/go/httptest.
func handleUpdateUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
// Connecting to the database
db, err := setupDB(user, password, host, dbname)
if err != nil {
panic(err)
}
defer db.Close()
// Parsing the request body as json
var userUpdateData User
if err := json.NewDecoder(r.Body).Decode(&userUpdateData); err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Update the record with ID 3
// u := User{ID: 3, Username: "newusername", Email: "newemail@example.com", Firstname: "New", Lastname: "Name", Sex: "male"}
err = updateUser(db, userUpdateData)
if err != nil {
panic(err)
}
fmt.Println("User updated successfully!")
// Sending a response indicating success
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(userUpdateData)
}
func updateUser(db *sql.DB, u User) error {
query := `
UPDATE users SET
username = $1,
email = $2,
firstname = $3,
lastname = $4,
sex = $5
WHERE id = $6;
`
_, err := db.Exec(query, u.Username, u.Email, u.Firstname, u.Lastname, u.Sex, u.ID)
if err != nil {
return err
}
return nil
}
func handleDeleteUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
// Connecting to the database
db, err := setupDB(user, password, host, dbname)
if err != nil {
panic(err)
}
defer db.Close()
// Parsing the request body as json
var userDateDel User
if err := json.NewDecoder(r.Body).Decode(&userDateDel); err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
// Update the record with ID 3
// u := User{ID: 3, Username: "newusername", Email: "newemail@example.com", Firstname: "New", Lastname: "Name", Sex: "male"}
err = deleteUser(db, userDateDel)
if err != nil {
panic(err)
}
fmt.Println("User updated successfully!")
// Sending a response indicating success
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(userDateDel)
}
func deleteUser(db *sql.DB, u User) error {
query := `DELETE FROM users WHERE id = $1`
_, err := db.Exec(query, u.ID)
if err != nil {
return err
}
return nil
}