In the [previous part](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-3), we made a back-end that is streaming blocks and detects 3 methods in custom_json. Create a game, request to join a game, and accept the request. We also set up a MySQL server with 3 tables for the methods.
You can find the links to the other parts at the end of the post.
### API
Let's start by implementing the API for retrieving the games list. Our API is public so it doesn't require user authentication. The API will just return the data synced from the blockchain.
`api/games.js`:
```
const mysql = require('../helpers/mysql')
const express = require('express')
const router = express.Router()
// Return all games on route /games
router.get('/games', async (req, res) => {
try {
// Get games data from Database
const games = await mysql.query(
'SELECT `game_id`, `player1`, `player2`, `starting_player`, `status`, `winner` FROM `games`'
)
// Check for expected result
if (!games || !Array.isArray(games) || games.length < 1) {
// WE return id: 1 for success and id: 0 for errors
return res.json({
id: 1,
games: []
})
}
// We return `games` as we receive from MySQL but it's not a good practice
// specially when you have critical data in the database
// You can return data one by one like `games: [{game_id: games.game_id, ...}]`
return res.json({
id: 1,
games
})
} catch (e) {
// Return error for unexpected errors like connection drops
res.json({
id: 0,
error: 'Unexpected error.'
})
}
})
module.exports = router
```
The comments are in the code itself for better understanding.
Note: We use `try{} catch{}` wherever we can. It is always good to handle errors.
We can test our API at this point to detect possible errors in the code.
Include the following code in `api/server.js` just above the `port` and `host` constants.
```
const games = require('./games')
app.use(games)
```
Run `node api/server.js`. We can see the console.log: `Application started on 127.0.0.1:2021`
Let's open `127.0.0.1:2021/games` in the browser.
The API works as expected.
But it's not done yet. This API returns ALL the games without a specific order. We should implement pagination and define an order for our list.
Updated code `api/games.js`:
```
const mysql = require('../helpers/mysql')
const express = require('express')
const router = express.Router()
router.get('/games/:page', async (req, res) => {
try {
if (isNaN(req.params.page)) {
res.json({
id: 0,
error: 'Expected number.'
})
}
const page = Math.floor(req.params.page)
if (page < 1) {
res.json({
id: 0,
error: 'Expected > 0'
})
}
const offset = (page - 1) * 10
const games = await mysql.query(
'SELECT `game_id`, `player1`, `player2`, `starting_player`, `status`, `winner` FROM `games`' +
' ORDER BY `id` DESC LIMIT 10 OFFSET ?',
[offset]
)
if (!games || !Array.isArray(games) || games.length < 1) {
return res.json({
id: 1,
games: []
})
}
return res.json({
id: 1,
games
})
} catch (e) {
res.json({
id: 0,
error: 'Unexpected error.'
})
}
})
module.exports = router
```
We used `id` to order our list and get the newly created games first. Each page returns up to 10 games. We can try `127.0.0.1:2021/games/1` for testing.
***
Let's set another API for requests. The code is almost similar but we return only requests for the specific game_id.
`api/requests.js`:
```
const mysql = require('../helpers/mysql')
const express = require('express')
const router = express.Router()
router.get('/requests/:id', async (req, res) => {
try {
if (!req.params.id) {
res.json({
id: 0,
error: 'Expected game_id.'
})
}
// We are passing user input into the database
// You should be careful in such cases
// We use ? for parameters which escapes the characters
const requests = await mysql.query(
'SELECT `player`, `status` FROM `requests` WHERE `game_id`= ?',
[req.params.id]
)
if (!requests || !Array.isArray(requests) || requests.length < 1) {
return res.json({
id: 1,
requests: []
})
}
return res.json({
id: 1,
requests
})
} catch (e) {
res.json({
id: 0,
error: 'Unexpected error.'
})
}
})
module.exports = router
```
Note: `:id` in the above router represents a variable named id. So for example `http://127.0.0.1:2021/requests/mygameidhere` in this request, the `id` variable is `mygameidhere` which is accessible by `req.params.id`.
***
A similar code for the `moves` table. There wasn't a better name in my mind for this table.
`api/moves.js`:
```
const mysql = require('../helpers/mysql')
const express = require('express')
const router = express.Router()
router.get('/moves/:id', async (req, res) => {
try {
if (!req.params.id) {
res.json({
id: 0,
error: 'Expected game_id.'
})
}
const moves = await mysql.query(
'SELECT `player`, `col`, `row` FROM `moves` WHERE `game_id`= ?',
[req.params.id]
)
if (!moves || !Array.isArray(moves) || moves.length < 1) {
return res.json({
id: 1,
moves: []
})
}
return res.json({
id: 1,
moves
})
} catch (e) {
res.json({
id: 0,
error: 'Unexpected error.'
})
}
})
module.exports = router
```
Now our 3 APIs are ready to be implemented on the front-end.
***
Here is the updated `api/server.js` after including the APIs:
```
const express = require('express')
const bodyParser = require('body-parser')
const hpp = require('hpp')
const helmet = require('helmet')
const app = express()
// more info: www.npmjs.com/package/hpp
app.use(hpp())
app.use(helmet())
// support json encoded bodies and encoded bodies
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(function (req, res, next) {
res.header(
'Access-Control-Allow-Origin',
'http://localhost https://tic-tac-toe.mahdiyari.info/'
)
res.header('Access-Control-Allow-Credentials', true)
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, access_key'
)
next()
})
// APIs
const games = require('./games')
const requests = require('./requests')
const moves = require('./moves')
app.use(games)
app.use(requests)
app.use(moves)
const port = process.env.PORT || 2021
const host = process.env.HOST || '127.0.0.1'
app.listen(port, host, () => {
console.log(`Application started on ${host}:${port}`)
})
```
***
### Front-end
I think using pure HTML is a mistake and I would prefer something like Angular for the web applications but that comes with its own learning process which can make this tutorial complex. So my recommendation is to learn something like Angular or Vue and live a happy life. Anyway, coding time.
I'm not going to drop `index.html` here. It doesn't need much explanation and it's long. You can see it on the [GitLab repository](https://gitlab.com/mahdiyari/decentralized-game-on-hive/-/blob/master/front-end/index.html). I will just add some references here used in `app.js`.
The table for listing the games and buttons for pagination.
`index.html`:
```
Games list
Auto updating every 5s
#
Game ID
Player 1
Player 2
Starting Player
Status
Winner
Action
```
***
We have to fill the table above. So let's implement some basic functions.
`js/app.js`:
```
const baseAPI = 'http://127.0.0.1:2021'
const APICall = async (api) => {
return (await fetch(baseAPI + api)).json()
}
```
For ease of use, we define a function for `GET` calls using `fetch` and a variable for our API address.
***
```
const getGames = async (page = 1) => {
const games = await APICall('/games/' + page)
return games.games
}
```
This function basically gets the games from the API per page.
***
```
const fillGamesTable = (data) => {
const tbody = document.getElementById('games-table-body')
let temp = ''
for (let i = 0; i < data.length; i++) {
temp += `
${(gamesPage - 1) * 10 + i + 1}
${data[i].game_id}
${data[i].player1}
${data[i].player2}
${data[i].starting_player}
${data[i].status}
${data[i].winner}
`
}
if (data.length < 1) {
temp = 'No games.'
}
tbody.innerHTML = temp
}
```
`fillGamesTable` takes the result from `getGames` function and fills the HTML table with data using a `for` loop.
***
```
let gamesPage = 1
const loadTheGames = async () => {
const games = await getGames(gamesPage)
fillGamesTable(games)
if (games.length < 10) {
document.getElementById('next-btn').className = 'page-item disabled'
} else {
document.getElementById('next-btn').className = 'page-item'
}
if (gamesPage === 1) {
document.getElementById('prev-btn').className = 'page-item disabled'
} else {
document.getElementById('prev-btn').className = 'page-item'
}
document.getElementById('page-number').innerHTML = ` ${gamesPage} `
}
loadTheGames()
setInterval(() => loadTheGames(), 5000)
```
With this function, we call the two previously defined functions to do their job and update the pagination buttons and the page number every time we update the table data. Also, every 5 seconds, it gets new data from API and updates the table with new data so users don't have to reload the page for new data.
***
```
const nextGamesPage = () => {
gamesPage++
loadTheGames()
}
const prevGamesPage = () => {
gamesPage--
loadTheGames()
}
```
And two functions for changing pages. Simple as that.
***
The final result with dummy data looks like this on the browser:
***
That's it for this part. I'm really happy with the results we are getting. I didn't plan anything beforehand and I'm coding as I'm writing the posts.
Today we made 3 API calls and created the basic front-end which for now only shows the list of games that are submitted to the Hive blockchain. In the next part, we will implement the methods for creating and joining the games on the client side. We also probably need to create a game page where actual gaming happens.
Thanks for reading. Make sure to follow me and share the post. Upvote if you like and leave a comment.
***
[GitLab](https://gitlab.com/mahdiyari/decentralized-game-on-hive)
[Part 1](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-tic-tac-toe-part-1)
[Part 2](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-2)
[Part 3](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-3)
[Next part >>](https://hive.blog/hive-169321/@mahdiyari/making-a-decentralized-game-on-hive-part-5)
***
**Vote for my witness:**
- https://wallet.hive.blog/~witnesses
- https://peakd.com/witnesses
- https://ecency.com/witnesses