Go Lang + ReactJS + Mysql CRUD example - Go Full stack development
Hello everyone, today we will learn how to develop a full-stack web application that is a basic User Management Application using React, Go, and Mysql.GitHub repository link is provided at the end of this tutorial. You can download the source code.
After completing this tutorial what we will build?
We will build a full-stack web application that is a basic User Management Application with CRUD features:
• Create User
• List All Users
• Update User
• Delete User
• View User
We divided this tutorial into two parts
PART 1 - Rest APIs Development using Go and Mysql
PART 2 - UI development using React JS
PART 1 - Rest APIs Development using Go Language
These are APIs that the Go Language application will export:
GET all User's : /users
GET User by ID : /users/{_id}
POST User : /users
PUT User : /users/{_id}
DELETE User : /users/{_id}
Backend Project Structure:
Create database 'userdb':
CREATE DATABASE userdb;
Create table 'users':
CREATE TABLE `users` (
`id` bigint(20) UNSIGNED NOT NULL,
`first_name` varchar(200) NOT NULL,
`last_name` varchar(200) NOT NULL,
`email` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `id` (`id`);
ALTER TABLE `users`
MODIFY `id` bigint(20) UNSIGNED NOT NULL
AUTO_INCREMENT, AUTO_INCREMENT=11;
Initialize the Go project:
Initialize the Go project using the following command
go mod init backend
Adding the modules required for the project:
go get github.com/gorilla/mux
go get github.com/go-sql-driver/mysql
main.go
package main
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
func main() {
Routers()
}
func Routers() {
InitDB()
defer db.Close()
router := mux.NewRouter()
router.HandleFunc("/users",
GetUsers).Methods("GET")
router.HandleFunc("/users",
CreateUser).Methods("POST")
router.HandleFunc("/users/{id}",
GetUser).Methods("GET")
router.HandleFunc("/users/{id}",
UpdateUser).Methods("PUT")
router.HandleFunc("/users/{id}",
DeleteUser).Methods("DELETE")
http.ListenAndServe(":9080",
&CORSRouterDecorator{router})
}
/***************************************************/
//Get all users
func GetUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var users []User
result, err := db.Query("SELECT id, first_name," +
"last_name,email from users")
if err != nil {
panic(err.Error())
}
defer result.Close()
for result.Next() {
var user User
err := result.Scan(&user.ID, &user.FirstName,
&user.LastName, &user.Email)
if err != nil {
panic(err.Error())
}
users = append(users, user)
}
json.NewEncoder(w).Encode(users)
}
//Create user
func CreateUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
stmt, err := db.Prepare("INSERT INTO users(first_name," +
"last_name,email) VALUES(?,?,?)")
if err != nil {
panic(err.Error())
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err.Error())
}
keyVal := make(map[string]string)
json.Unmarshal(body, &keyVal)
first_name := keyVal["firstName"]
last_name := keyVal["lastName"]
email := keyVal["email"]
_, err = stmt.Exec(first_name, last_name, email)
if err != nil {
panic(err.Error())
}
fmt.Fprintf(w, "New user was created")
}
//Get user by ID
func GetUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
result, err := db.Query("SELECT id, first_name,"+
"last_name,email from users WHERE id = ?", params["id"])
if err != nil {
panic(err.Error())
}
defer result.Close()
var user User
for result.Next() {
err := result.Scan(&user.ID, &user.FirstName,
&user.LastName, &user.Email)
if err != nil {
panic(err.Error())
}
}
json.NewEncoder(w).Encode(user)
}
//Update user
func UpdateUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
stmt, err := db.Prepare("UPDATE users SET first_name = ?," +
"last_name= ?, email=? WHERE id = ?")
if err != nil {
panic(err.Error())
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err.Error())
}
keyVal := make(map[string]string)
json.Unmarshal(body, &keyVal)
first_name := keyVal["firstName"]
last_name := keyVal["lastName"]
email := keyVal["email"]
_, err = stmt.Exec(first_name, last_name, email,
params["id"])
if err != nil {
panic(err.Error())
}
fmt.Fprintf(w, "User with ID = %s was updated",
params["id"])
}
//Delete User
func DeleteUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
stmt, err := db.Prepare("DELETE FROM users WHERE id = ?")
if err != nil {
panic(err.Error())
}
_, err = stmt.Exec(params["id"])
if err != nil {
panic(err.Error())
}
fmt.Fprintf(w, "User with ID = %s was deleted",
params["id"])
}
/***************************************************/
type User struct {
ID string `json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
}
//Db configuration
var db *sql.DB
var err error
func InitDB() {
db, err = sql.Open("mysql",
"root:@tcp(127.0.0.1:3306)/userdb")
if err != nil {
panic(err.Error())
}
}
/***************************************************/
// CORSRouterDecorator applies CORS headers to a mux.Router
type CORSRouterDecorator struct {
R *mux.Router
}
func (c *CORSRouterDecorator) ServeHTTP(rw http.ResponseWriter,
req *http.Request) {
if origin := req.Header.Get("Origin"); origin != "" {
rw.Header().Set("Access-Control-Allow-Origin", origin)
rw.Header().Set("Access-Control-Allow-Methods",
"POST, GET, OPTIONS, PUT, DELETE")
rw.Header().Set("Access-Control-Allow-Headers",
"Accept, Accept-Language,"+
" Content-Type, YourOwnHeader")
}
// Stop here if its Preflighted OPTIONS request
if req.Method == "OPTIONS" {
return
}
c.R.ServeHTTP(rw, req)
}
Run:
go run main.go
PART 2 - UI development using React JS
Front Project Structure:
package.json
A package.json is a JSON file that subsists at the root of a Javascript/Node project. It holds metadata pertinent to the project and is utilized for managing the project's dependencies, scripts, version, and a whole lot more.
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"axios": "^0.19.2",
"bootstrap": "^4.5.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
CreateUserComponent.jsx
import React, { Component } from 'react'
import UserService from '../services/UserService';
class CreateUserComponent extends Component {
constructor(props) {
super(props)
this.state = {
// step 2
id: this.props.match.params.id,
firstName: '',
lastName: '',
email: ''
}
this.changeFirstNameHandler =
this.changeFirstNameHandler.bind(this);
this.changeLastNameHandler =
this.changeLastNameHandler.bind(this);
this.saveOrUpdateUser =
this.saveOrUpdateUser.bind(this);
}
// step 3
componentDidMount() {
// step 4
if (this.state.id === '_add') {
return
} else {
UserService.getUserById(this.state.id).
then((res) => {
let user = res.data;
this.setState({
firstName: user.firstName,
lastName: user.lastName,
email: user.email
});
});
}
}
saveOrUpdateUser = (e) => {
e.preventDefault();
let user = { firstName: this.state.firstName, lastName:
this.state.lastName, email: this.state.email };
console.log('user => ' + JSON.stringify(user));
// step 5
if (this.state.id === '_add') {
UserService.createUser(user).then(res => {
this.props.history.push('/users');
});
} else {
UserService.
updateUser(user, this.state.id).then(res => {
this.props.history.push('/users');
});
}
}
changeFirstNameHandler = (event) => {
this.setState({ firstName: event.target.value });
}
changeLastNameHandler = (event) => {
this.setState({ lastName: event.target.value });
}
changeEmailHandler = (event) => {
this.setState({ email: event.target.value });
}
cancel() {
this.props.history.push('/users');
}
getTitle() {
if (this.state.id === '_add') {
return <h3 className="text-center">Add User</h3>
} else {
return <h3 className="text-center">Update User</h3>
}
}
render() {
return (
<div>
<br></br>
<div className="container">
<div className="row">
<div className="card col-md-6 offset-md-3 offset-md-3">
{
this.getTitle()
}
<div className="card-body">
<form>
<div className="form-group">
<label> First Name: </label>
<input placeholder="First Name"
name="firstName" className="form-control"
value={this.state.firstName}
onChange={this.changeFirstNameHandler} />
</div>
<div className="form-group">
<label> Last Name: </label>
<input placeholder="Last Name"
name="lastName" className="form-control"
value={this.state.lastName}
onChange={this.changeLastNameHandler} />
</div>
<div className="form-group">
<label> Email : </label>
<input placeholder="Email Address"
name="email" className="form-control"
value={this.state.email}
onChange={this.changeEmailHandler} />
</div>
<button className="btn btn-success"
onClick={this.saveOrUpdateUser}>Save
</button>
<button className="btn btn-danger"
onClick={this.cancel.bind(this)}
style={{ marginLeft: "10px" }}>Cancel
</button>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default CreateUserComponent
ListUserComponent.jsx
import React, { Component } from 'react'
import UserService from '../services/UserService'
class ListUserComponent extends Component {
constructor(props) {
super(props)
this.state = {
users: []
}
this.addUser = this.addUser.bind(this);
this.editUser = this.editUser.bind(this);
this.deleteUser = this.deleteUser.bind(this);
}
deleteUser(id){
UserService.deleteUser(id).then( res => {
this.setState({users:
this.state.users.
filter(user => user.id !== id)});
});
}
viewUser(id){
this.props.history.push(`/view-user/${id}`);
}
editUser(id){
this.props.history.push(`/add-user/${id}`);
}
componentDidMount(){
UserService.getUsers().then((res) => {
if(res.data==null)
{
this.props.history.push('/add-user/_add');
}
this.setState({ users: res.data});
});
}
addUser(){
this.props.history.push('/add-user/_add');
}
render() {
return (
<div>
<h2 className="text-center">
Users List</h2>
<div className = "row">
<button className="btn btn-primary"
onClick={this.addUser}> Add User</button>
</div>
<br></br>
<div className = "row">
<table className
= "table table-striped table-bordered">
<thead>
<tr>
<th> User First Name</th>
<th> User Last Name</th>
<th> User Email </th>
<th> Actions</th>
</tr>
</thead>
<tbody>
{
this.state.users.map(
user =>
<tr key = {user.id}>
<td> { user.firstName} </td>
<td> {user.lastName}</td>
<td> {user.email}</td>
<td>
<button onClick={ () =>
this.editUser(user.id)}
className="btn btn-info">Update
</button>
<button style={{marginLeft: "10px"}}
onClick={ () => this.deleteUser(user.id)}
className="btn btn-danger">Delete
</button>
<button style={{marginLeft: "10px"}}
onClick={ () => this.viewUser(user.id)}
className="btn btn-info">View
</button>
</td>
</tr>
)
}
</tbody>
</table>
</div>
</div>
)
}
}
export default ListUserComponent
ViewUserComponent.jsx
import React, { Component } from 'react'
import UserService from '../services/UserService'
class ViewUserComponent extends Component {
constructor(props) {
super(props)
this.state = {
id: this.props.match.params.id,
user: {}
}
}
componentDidMount(){
UserService.getUserById(this.state.id).then( res => {
this.setState({user: res.data});
})
}
render() {
return (
<div>
<br></br>
<div className = "card col-md-6 offset-md-3">
<h3 className = "text-center">
View User Details</h3>
<div className = "card-body">
<div className = "row">
<label> User First Name: </label>
<div> { this.state.user.firstName }
</div>
</div>
<div className = "row">
<label> User Last Name: </label>
<div> { this.state.user.lastName }
</div>
</div>
<div className = "row">
<label> User Email : </label>
<div> { this.state.user.email }
</div>
</div>
</div>
</div>
</div>
)
}
}
export default ViewUserComponent
FooterComponent.jsx
import React, { Component } from 'react'
class FooterComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div>
<footer className = "footer">
<span className="text-muted">
All Rights Reserved 2021
@Knowledgefactory.net</span>
</footer>
</div>
)
}
}
export default FooterComponent
HeaderComponent.js
import React, { Component } from 'react'
import 'bootstrap/dist/css/bootstrap.min.css';
class HeaderComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div>
<header>
<nav
className="navbar navbar-dark bg-primary">
<div>
<a href="/users"
className="navbar-brand">
User Management App
</a></div>
</nav>
</header>
</div>
)
}
}
export default HeaderComponent
UserService.js
import axios from 'axios';
const USER_API_BASE_URL = "http://localhost:9080/users";
class UserService {
getUsers(){
return axios.get(USER_API_BASE_URL);
}
createUser(user){
return axios.post(USER_API_BASE_URL, user);
}
getUserById(userId){
return axios.get(USER_API_BASE_URL + '/' + userId);
}
updateUser(user, userId){
return axios.put(USER_API_BASE_URL + '/' + userId, user);
}
deleteUser(userId){
return axios.delete(USER_API_BASE_URL + '/' + userId);
}
}
export default new UserService()
App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.footer {
position: absolute;
bottom: 0;
width:100%;
height: 50px;
background-color: black;
text-align: center;
color: white;
}
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import {BrowserRouter as Router, Route, Switch}
from 'react-router-dom'
import ListUserComponent from './components/ListUserComponent';
import HeaderComponent from './components/HeaderComponent';
import FooterComponent from './components/FooterComponent';
import CreateUserComponent from './components/CreateUserComponent';
import ViewUserComponent from './components/ViewUserComponent';
function App() {
return (
<div>
<Router>
<HeaderComponent />
<div className="container">
<Switch>
<Route path = "/" exact component =
{ListUserComponent}></Route>
<Route path = "/users" component =
{ListUserComponent}></Route>
<Route path = "/add-user/:id" component =
{CreateUserComponent}></Route>
<Route path = "/view-user/:id" component =
{ViewUserComponent}></Route>
</Switch>
</div>
<FooterComponent />
</Router>
</div>
);
}
export default App;
index.css
body {
margin: 0;
font-family: -apple-system,
BlinkMacSystemFont, 'Segoe UI',
'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell',
'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro,
Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'bootstrap/dist/css/bootstrap.min.css';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width,
initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon"
href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest"
href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript
to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Frontend Local Setup
Step 1: The npm install installs all modules that are listed on package.json file and their
dependencies
npm install
Step 2: Run the Frontend application
npm start
App running at:
- Local:http://localhost:3000/