├── .gitignore ├── .env.example ├── go-postgres.exe ├── go.mod ├── models └── models.go ├── main.go ├── go.sum ├── .github └── FUNDING.yml ├── router └── router.go ├── README.md └── middleware └── handlers.go /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | POSTGRES_URL="Postgres connection string" -------------------------------------------------------------------------------- /go-postgres.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schadokar/go-postgres/HEAD/go-postgres.exe -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go-postgres 2 | 3 | require ( 4 | github.com/gorilla/mux v1.7.4 5 | github.com/joho/godotenv v1.3.0 6 | github.com/lib/pq v1.3.0 7 | ) 8 | -------------------------------------------------------------------------------- /models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // User schema of the user table 4 | type User struct { 5 | ID int64 `json:"id"` 6 | Name string `json:"name"` 7 | Location string `json:"location"` 8 | Age int64 `json:"age"` 9 | } 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go-postgres/router" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | r := router.Router() 12 | // fs := http.FileServer(http.Dir("build")) 13 | // http.Handle("/", fs) 14 | fmt.Println("Starting server on the port 8080...") 15 | 16 | log.Fatal(http.ListenAndServe(":8080", r)) 17 | } 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 2 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 4 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 5 | github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= 6 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: #[schadokar] 4 | patreon: schadokar 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: schadokar 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "go-postgres/middleware" 5 | 6 | "github.com/gorilla/mux" 7 | ) 8 | 9 | // Router is exported and used in main.go 10 | func Router() *mux.Router { 11 | 12 | router := mux.NewRouter() 13 | 14 | router.HandleFunc("/api/user/{id}", middleware.GetUser).Methods("GET", "OPTIONS") 15 | router.HandleFunc("/api/user", middleware.GetAllUser).Methods("GET", "OPTIONS") 16 | router.HandleFunc("/api/newuser", middleware.CreateUser).Methods("POST", "OPTIONS") 17 | router.HandleFunc("/api/user/{id}", middleware.UpdateUser).Methods("PUT", "OPTIONS") 18 | router.HandleFunc("/api/deleteuser/{id}", middleware.DeleteUser).Methods("DELETE", "OPTIONS") 19 | 20 | return router 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-Postgres 2 | 3 | This project is simple CRUD application built in golang and using PostgreSQL as DB. 4 | This project is explained in this [tutorial]. 5 | 6 | ## Pre-requisite 7 | 1. Install golang v1.11 or above. 8 | 2. Basic understanding of the golang syntax. 9 | 3. Basic understanding of SQL query. 10 | 4. Code Editor (I recommend to use VS Code with Go extension by Microsoft installed) 11 | 5. Postman for calling APIs 12 | 13 | ## PostgreSQL Table 14 | 15 | ```sql 16 | CREATE TABLE users ( 17 | userid SERIAL PRIMARY KEY, 18 | name TEXT, 19 | age INT, 20 | location TEXT 21 | ); 22 | ``` 23 | 24 | ## Author 25 | 26 | I am Shubham Chadokar. I am Software Engineer and work mostly on backend development. 27 | I love write the articles and tutorials on Golang, Nodejs, Blockhain. 28 | You can read all my articles and tutorials [here](https://schadokar.dev). 29 | -------------------------------------------------------------------------------- /middleware/handlers.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" // package to encode and decode the json into struct and vice versa 6 | "fmt" 7 | "go-postgres/models" // models package where User schema is defined 8 | "log" 9 | "net/http" // used to access the request and response object of the api 10 | "os" // used to read the environment variable 11 | "strconv" // package used to covert string into int type 12 | 13 | "github.com/gorilla/mux" // used to get the params from the route 14 | 15 | "github.com/joho/godotenv" // package used to read the .env file 16 | _ "github.com/lib/pq" // postgres golang driver 17 | ) 18 | 19 | // response format 20 | type response struct { 21 | ID int64 `json:"id,omitempty"` 22 | Message string `json:"message,omitempty"` 23 | } 24 | 25 | // create connection with postgres db 26 | func createConnection() *sql.DB { 27 | // load .env file 28 | err := godotenv.Load(".env") 29 | 30 | if err != nil { 31 | log.Fatalf("Error loading .env file") 32 | } 33 | 34 | // Open the connection 35 | db, err := sql.Open("postgres", os.Getenv("POSTGRES_URL")) 36 | 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | // check the connection 42 | err = db.Ping() 43 | 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | fmt.Println("Successfully connected!") 49 | // return the connection 50 | return db 51 | } 52 | 53 | // CreateUser create a user in the postgres db 54 | func CreateUser(w http.ResponseWriter, r *http.Request) { 55 | 56 | // create an empty user of type models.User 57 | var user models.User 58 | 59 | // decode the json request to user 60 | err := json.NewDecoder(r.Body).Decode(&user) 61 | 62 | if err != nil { 63 | log.Fatalf("Unable to decode the request body. %v", err) 64 | } 65 | 66 | // call insert user function and pass the user 67 | insertID := insertUser(user) 68 | 69 | // format a response object 70 | res := response{ 71 | ID: insertID, 72 | Message: "User created successfully", 73 | } 74 | 75 | // send the response 76 | json.NewEncoder(w).Encode(res) 77 | } 78 | 79 | // GetUser will return a single user by its id 80 | func GetUser(w http.ResponseWriter, r *http.Request) { 81 | // get the userid from the request params, key is "id" 82 | params := mux.Vars(r) 83 | 84 | // convert the id type from string to int 85 | id, err := strconv.Atoi(params["id"]) 86 | 87 | if err != nil { 88 | log.Fatalf("Unable to convert the string into int. %v", err) 89 | } 90 | 91 | // call the getUser function with user id to retrieve a single user 92 | user, err := getUser(int64(id)) 93 | 94 | if err != nil { 95 | log.Fatalf("Unable to get user. %v", err) 96 | } 97 | 98 | // send the response 99 | json.NewEncoder(w).Encode(user) 100 | } 101 | 102 | // GetAllUser will return all the users 103 | func GetAllUser(w http.ResponseWriter, r *http.Request) { 104 | 105 | // get all the users in the db 106 | users, err := getAllUsers() 107 | 108 | if err != nil { 109 | log.Fatalf("Unable to get all user. %v", err) 110 | } 111 | 112 | // send all the users as response 113 | json.NewEncoder(w).Encode(users) 114 | } 115 | 116 | // UpdateUser update user's detail in the postgres db 117 | func UpdateUser(w http.ResponseWriter, r *http.Request) { 118 | 119 | // get the userid from the request params, key is "id" 120 | params := mux.Vars(r) 121 | 122 | // convert the id type from string to int 123 | id, err := strconv.Atoi(params["id"]) 124 | 125 | if err != nil { 126 | log.Fatalf("Unable to convert the string into int. %v", err) 127 | } 128 | 129 | // create an empty user of type models.User 130 | var user models.User 131 | 132 | // decode the json request to user 133 | err = json.NewDecoder(r.Body).Decode(&user) 134 | 135 | if err != nil { 136 | log.Fatalf("Unable to decode the request body. %v", err) 137 | } 138 | 139 | // call update user to update the user 140 | updatedRows := updateUser(int64(id), user) 141 | 142 | // format the message string 143 | msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", updatedRows) 144 | 145 | // format the response message 146 | res := response{ 147 | ID: int64(id), 148 | Message: msg, 149 | } 150 | 151 | // send the response 152 | json.NewEncoder(w).Encode(res) 153 | } 154 | 155 | // DeleteUser delete user's detail in the postgres db 156 | func DeleteUser(w http.ResponseWriter, r *http.Request) { 157 | 158 | // get the userid from the request params, key is "id" 159 | params := mux.Vars(r) 160 | 161 | // convert the id in string to int 162 | id, err := strconv.Atoi(params["id"]) 163 | 164 | if err != nil { 165 | log.Fatalf("Unable to convert the string into int. %v", err) 166 | } 167 | 168 | // call the deleteUser, convert the int to int64 169 | deletedRows := deleteUser(int64(id)) 170 | 171 | // format the message string 172 | msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", deletedRows) 173 | 174 | // format the reponse message 175 | res := response{ 176 | ID: int64(id), 177 | Message: msg, 178 | } 179 | 180 | // send the response 181 | json.NewEncoder(w).Encode(res) 182 | } 183 | 184 | //------------------------- handler functions ---------------- 185 | // insert one user in the DB 186 | func insertUser(user models.User) int64 { 187 | 188 | // create the postgres db connection 189 | db := createConnection() 190 | 191 | // close the db connection 192 | defer db.Close() 193 | 194 | // create the insert sql query 195 | // returning userid will return the id of the inserted user 196 | sqlStatement := `INSERT INTO users (name, location, age) VALUES ($1, $2, $3) RETURNING userid` 197 | 198 | // the inserted id will store in this id 199 | var id int64 200 | 201 | // execute the sql statement 202 | // Scan function will save the insert id in the id 203 | err := db.QueryRow(sqlStatement, user.Name, user.Location, user.Age).Scan(&id) 204 | 205 | if err != nil { 206 | log.Fatalf("Unable to execute the query. %v", err) 207 | } 208 | 209 | fmt.Printf("Inserted a single record %v", id) 210 | 211 | // return the inserted id 212 | return id 213 | } 214 | 215 | // get one user from the DB by its userid 216 | func getUser(id int64) (models.User, error) { 217 | // create the postgres db connection 218 | db := createConnection() 219 | 220 | // close the db connection 221 | defer db.Close() 222 | 223 | // create a user of models.User type 224 | var user models.User 225 | 226 | // create the select sql query 227 | sqlStatement := `SELECT * FROM users WHERE userid=$1` 228 | 229 | // execute the sql statement 230 | row := db.QueryRow(sqlStatement, id) 231 | 232 | // unmarshal the row object to user 233 | err := row.Scan(&user.ID, &user.Name, &user.Age, &user.Location) 234 | 235 | switch err { 236 | case sql.ErrNoRows: 237 | fmt.Println("No rows were returned!") 238 | return user, nil 239 | case nil: 240 | return user, nil 241 | default: 242 | log.Fatalf("Unable to scan the row. %v", err) 243 | } 244 | 245 | // return empty user on error 246 | return user, err 247 | } 248 | 249 | // get one user from the DB by its userid 250 | func getAllUsers() ([]models.User, error) { 251 | // create the postgres db connection 252 | db := createConnection() 253 | 254 | // close the db connection 255 | defer db.Close() 256 | 257 | var users []models.User 258 | 259 | // create the select sql query 260 | sqlStatement := `SELECT * FROM users` 261 | 262 | // execute the sql statement 263 | rows, err := db.Query(sqlStatement) 264 | 265 | if err != nil { 266 | log.Fatalf("Unable to execute the query. %v", err) 267 | } 268 | 269 | // close the statement 270 | defer rows.Close() 271 | 272 | // iterate over the rows 273 | for rows.Next() { 274 | var user models.User 275 | 276 | // unmarshal the row object to user 277 | err = rows.Scan(&user.ID, &user.Name, &user.Age, &user.Location) 278 | 279 | if err != nil { 280 | log.Fatalf("Unable to scan the row. %v", err) 281 | } 282 | 283 | // append the user in the users slice 284 | users = append(users, user) 285 | 286 | } 287 | 288 | // return empty user on error 289 | return users, err 290 | } 291 | 292 | // update user in the DB 293 | func updateUser(id int64, user models.User) int64 { 294 | 295 | // create the postgres db connection 296 | db := createConnection() 297 | 298 | // close the db connection 299 | defer db.Close() 300 | 301 | // create the update sql query 302 | sqlStatement := `UPDATE users SET name=$2, location=$3, age=$4 WHERE userid=$1` 303 | 304 | // execute the sql statement 305 | res, err := db.Exec(sqlStatement, id, user.Name, user.Location, user.Age) 306 | 307 | if err != nil { 308 | log.Fatalf("Unable to execute the query. %v", err) 309 | } 310 | 311 | // check how many rows affected 312 | rowsAffected, err := res.RowsAffected() 313 | 314 | if err != nil { 315 | log.Fatalf("Error while checking the affected rows. %v", err) 316 | } 317 | 318 | fmt.Printf("Total rows/record affected %v", rowsAffected) 319 | 320 | return rowsAffected 321 | } 322 | 323 | // delete user in the DB 324 | func deleteUser(id int64) int64 { 325 | 326 | // create the postgres db connection 327 | db := createConnection() 328 | 329 | // close the db connection 330 | defer db.Close() 331 | 332 | // create the delete sql query 333 | sqlStatement := `DELETE FROM users WHERE userid=$1` 334 | 335 | // execute the sql statement 336 | res, err := db.Exec(sqlStatement, id) 337 | 338 | if err != nil { 339 | log.Fatalf("Unable to execute the query. %v", err) 340 | } 341 | 342 | // check how many rows affected 343 | rowsAffected, err := res.RowsAffected() 344 | 345 | if err != nil { 346 | log.Fatalf("Error while checking the affected rows. %v", err) 347 | } 348 | 349 | fmt.Printf("Total rows/record affected %v", rowsAffected) 350 | 351 | return rowsAffected 352 | } 353 | --------------------------------------------------------------------------------