How to Solve Sudoku Puzzles Using an API: A Step-by-Step Guide (Part-1)

•

Mon Apr 03 2023

•

19 min read

This step-by-step guide provides instructions on how to create a Solve Sudoku API using the RapidAPI Hub.

The resulting API will enable us to build a personalised Sudoku solver. The RapidAPI Hub is an optimal choice because it can access thousands of APIs using just one API key, making it incredibly convenient. So, let's dive in and get started!

We will use JavaScript to construct a Sudoku grid and allow the users to input digits from their own Sudoku puzzles to solve. After inputting the digits, the user can click the "solve" button, activating the API's core to solve the Sudoku puzzle.

In this guide's next part, we will focus on building a secure mini backend in Node.js to store your API keys.

Loading component...

Sudoku Solver API on RapidAPI Hub

In order to assist us in solving our Sudoku puzzles, I have located an appropriate Solve Sudoku API on the RapidAPI Hub. Click on this link to register for this API and test its endpoints with me.

The request on the right side of the page represents a post request sent to the URL: 'https://solve-sudokdu.p.rapidapi.com'. The request includes a puzzle parameter that is essentially a string representation of the Sudoku puzzle to be solved.

In the example provided, "puzzle": "2.............62....1....7...6..8...3...9...7...6" represents the initial Sudoku puzzle, where digits represent numbers and empty spaces are represented by dots. The API will solve the puzzle and return the complete Sudoku puzzle as a response.

Once you click on the Test Endpoint option, you can evaluate the endpoint's functionality. The response will be returned as a string, which can be utilized to map out the solution onto the puzzle's squares. This will allow for a visual representation of the solution.

To utilize the code mentioned, we must first create a project. Please note that the 'x-rapidapi-key' provided in the example is specific to the user and should not be used. Instead, please obtain your API key.

After obtaining your own API key, create a project and paste the code into your project to use the Sudoku Solver API.

Creating a New Project and File for Sudoku Solver

To begin, create a new project in the following steps for developing Sudoku API. Choose a simple JavaScript project and name it 'Sudoku Solver'. Then, create a new file to begin writing the code for the Sudoku Solver API.

Step 1

To start, create an index.html file for the Sudoku Solver project. Include a link tag to the styles.css file within the HTML file. Make sure the href contains the correct file path to the styles.css file, which should be located at the root of the project. Additionally, include a script tag to link the JavaScript file to the HTML file.

To create the app.js file, navigate to the project section and create a new file named app.js and the JavaScript file type.

After creating the file, ensure you have the styles.css, index.html, and app.js files in your repository.

Below is an example HTML code snippet with a linked styles.css file and an app.js file. This code should be added to the index.html file to ensure the files are linked correctly.

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sudoku Solver</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<script src="app.js"></script>
</body>
</html>

We will create a div in the HTML file with an id of puzzle. This is where JavaScript will populate the Sudoku grid. We will also add a button with an id of solve-button so that users can click on it to solve the puzzle.

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sudoku Solver</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="puzzle"></div>
<button id="solve-button">Solve</button>
<script src="app.js"></script>
</body>
</html>

Step 2

We will navigate to the app.js file and extract both. To accomplish this, I will utilize the document.querySelector method, which will look for the element with the id of the puzzle. Similarly, I will use document.querySelector again to find the element with the id of solve-button. After that, I will store these as const functions so that we can utilize them later.

Here's an example code snippet to get the DOM elements for the puzzle and solve-button:

js
const puzzleElement = document.querySelector("#puzzle");
const solveButton = document.querySelector("#solve-button");

Step 3

After examining the Sudoku-Solver API, I have determined that I need 81 squares, as it is a 9x9 grid. As a result, I will create a const called squares and set it equal to 81.

Next, we will generate the squares we want to add to our puzzle. To achieve this, I will use a for loop with i starting at 0, and the loop will continue until squares are less than 81. Each iteration will increment i++.

This implies that for each of the 81 squares, we want to create an input element using document.createElement(). We will pass the string input as an argument to create and save an input element.

In addition, I want to set the inputElement to have a setAttribute of type and number. Also, I want to define a function to limit the input range to only 0 to 9 by setting the min value to 0 and the max value to 9. This ensures that the input values for each square in the Sudoku puzzle are restricted to only numbers between 0 and 9, as higher numbers or negative numbers are not valid for Sudoku puzzles.

Next, we will append the inputElement to the end of the puzzleBoard element. This means that the inputElement will be added as a child element of the puzzleBoard element, which will appear inside the container element as a cell of the Sudoku puzzle grid.

js
const puzzleBoard = document.querySelector('#puzzle');
const solveButton = document.querySelector('#solve-button');
const squares = 81;
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input');
inputElement.setAttribute('type', 'number');
inputElement.setAttribute('min', '1');
inputElement.setAttribute('max', '9');
puzzleBoard.appendChild(inputElement);
}

Let's take a look at the result of our code. For each iteration of the loop, we have created and appended an input element to the puzzleBoard container element. This will result in a 9x9 grid of input cells representing the initial state of the Sudoku puzzle.

Step 4

Let's proceed to customize the appearance of the Sudoku puzzle grid to make it look like a square by defining appropriate dimensions for both the input elements and the puzzle board container.

We will assign specific widths and heights to the input cells and the puzzle board container. To achieve this, we'll navigate to our style.css file and select the element with the id of puzzle and all inputs that are children of this element. We will then set the width of each input to 50px and the height to 50px as well.

This means that the puzzle board container will have a width of 450px and a height of 450px since there are 9 cells in each row and 9 rows. Before proceeding, we need to fix a problem where the inputs are accounting for their borders, causing some styling issues.

To avoid styling issues, I will set the box-sizing property of the elements with puzzle id and its child inputs to border-box, which will include the border and padding inside the element's total width and height.

I will also set the border-spacing property to 0 to remove the extra space between cells. Finally, I will set the border property of the inputs to 1px solid grey to create a thin border around each input cell.

css
#puzzle {
width: 450px;
height: 450px;
}
#puzzle input {
width: 50px;
height: 50px;
box-sizing: border-box;
border-spacing: 0;
border: 1px solid grey;
}

Let's take a look at the result, and it appears to be a significant improvement.

Step 5

To collect all the values from the inputs and convert them into an array for solving the Sudoku puzzle, I will create a joinvalues function and declare it as a const. To select all the input elements from the HTML, I will use document.querySelectorAll() and store them in a const variable called inputs.

After creating the puzzle board with input elements, you can add an event listener to the "Solve" button. Inside the event listener function, you can iterate through all the input elements in the puzzle board and create an array called submission, which will contain the current state of the puzzle.

For each input element, you can check if it has a value or not. You can push it into the submission array if it has a value. Otherwise, if it doesn't have a value, you can push a "." character into the submission array to indicate an empty square. This will help you create a representation of the puzzle, which can be sent to the Sudoku-Solver API for solving.

Additionally, I will include a console.log(submission) statement. Then, I will use addEventListener() to get the solvebutton. When clicking the button, I want to execute the joinValues function.

js
const puzzleBoard = document.querySelector('#puzzle')
const solveButton = document.querySelector('#solve-button')
const squares = 81
const submission = []
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input')
inputElement.setAttribute('type', 'number')
inputElement.setAttribute('min', 0)
inputElement.setAttribute('max', 9)
puzzleBoard.appendChild(inputElement)
}
const joinValues = () => {
const inputs = document.querySelectorAll('input')
inputs.forEach(input => {
if (input.value) {
submission.push(input.value)
} else {
submission.push('.')
}
})
console.log(submission)
}
solveButton.addEventListener('click', joinValues)

Let's test the program by filling in some random numbers in the grid and clicking the solve button. When we do this, the response of the array will be displayed.

Step 6

To proceed, we have an array of values that need to be converted into a string. We will be using some JavaScript methods to accomplish this.

Let's return to our app.js file and update the minimum value to 1, as we don't want to pass through zeros. Once all values are joined, we will use a JavaScript method to combine them into a single string, which will then be sent to the API.

A new function called solve will be created to call the Sudoku-Solver API. The code for this function will be copied and pasted from the code snippets available on the Solve Sudoku API page on RapidAPI Hub. This code will be added to the app.js file.

js
const puzzleBoard = document.querySelector('#puzzle');
const solveButton = document.querySelector('#solve-button');
const squares = 81;
const submission = [];
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input');
inputElement.setAttribute('type', 'number');
inputElement.setAttribute('min', 1);
inputElement.setAttribute('max', 9);
puzzleBoard.appendChild(inputElement);
}
const joinValues = () => {
const inputs = document.querySelectorAll('input');
inputs.forEach(input => {
if (input.value) {
submission.push(input.value);
} else {
submission.push('.');
}
});
console.log(submission);
};
const solve = () => {
var axios = require('axios').default;
var options = {
method: 'POST',
url: 'https://solve-sudoku.p.rapidapi.com/',
headers: {
'content-type': 'application/json',
'x-rapidapi-host': 'solve-sudoku.p.rapidapi.com',
'x-rapidapi-key': '6835772f3emsh8dbf271d59b19eep132ed4isndd226502a839'
},
data: {
puzzle: '2……….62….1….7…6..8…3…9…7…6..4…4…8….52……..'
}
};
axios.request(options).then(function(response) {
console.log(response.data);
}).catch(function(error) {
console.error(error);
});
};
solveButton.addEventListener('click', joinValues);

To use Axios there are multiple ways to do it, but I will add a script tag in the HTML file to include the Axios package.

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sudoku Solver</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="puzzle"></div>
<button id="solve-button">Solve</button>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="app.js"></script>
</body>
</html>

Some modifications to our code in app.js are made to utilise the Axios package. The joinValues are replaced with solve.

js
const puzzleBoard = document.querySelector('#puzzle');
const solveButton = document.querySelector('#solve-button');
const squares = 81;
const submission = [];
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input');
inputElement.setAttribute('type', 'number');
inputElement.setAttribute('min', 1);
inputElement.setAttribute('max', 9);
puzzleBoard.appendChild(inputElement);
}
const joinValues = () => {
const inputs = document.querySelectorAll('input');
inputs.forEach(input => {
if (input.value) {
submission.push(input.value);
} else {
submission.push('.');
}
});
console.log(submission);
};
const solve = () => {
var options = {
method: 'POST',
url: 'https://solve-sudoku.p.rapidapi.com/',
headers: {
'content-type': 'application/json',
'x-rapidapi-host': 'solve-sudoku.p.rapidapi.com',
'x-rapidapi-key': '6835772f3emsh8dbf271d59b19eep132ed4isndd226502a839'
},
data: {
puzzle: '2……….62….1….7…6..8…3…9…7…6..4…4…8….52……..'
}
};
axios.request(options).then(function(response) {
console.log(response.data);
}).catch(function(error) {
console.error(error);
});
};
solveButton.addEventListener('click', solve);

We will now test if the changes we made are working properly. We can do this by clicking the solve button, as it doesn't matter what values are in the grid since we are passing a hard-coded string. After clicking the button, we should see the solved string as the output.

Step 7

We will make some modifications to the code to improve its readability. Instead of using regular functions, we will be using arrow functions. Moreover, clicking the solve button will call the Join Values function to retrieve the submissions and then concatenate them using the join method available in JavaScript.

js
const puzzleBoard = document.querySelector('#puzzle');
const solveButton = document.querySelector('#solve-button');
const squares = 81;
const submission = [];
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input');
inputElement.setAttribute('type', 'number');
inputElement.setAttribute('min', 1);
inputElement.setAttribute('max', 9);
puzzleBoard.appendChild(inputElement);
}
const joinValues = () => {
const inputs = document.querySelectorAll('input');
submission.length = 0; // clear previous values
inputs.forEach(input => {
if (input.value) {
submission.push(input.value);
} else {
submission.push('.');
}
});
console.log(submission);
};
const solve = () => {
joinValues();
const data = submission.join('');
console.log('data', data);
const options = {
method: 'POST',
url: 'https://solve-sudoku.p.rapidapi.com/',
headers: {
'content-type': 'application/json',
'x-rapidapi-host': 'solve-sudoku.p.rapidapi.com',
'x-rapidapi-key': '6835772f3emsh8dbf271d59b19eep132ed4isndd226502a839'
},
data: {
puzzle: data
}
};
axios.request(options).then((response) => {
console.log(response.data);
}).catch((error) => {
console.error(error);
});
};
solveButton.addEventListener('click', solve);

Let's test the code and observe the output. The output displays the string we passed in and confirms that it is solvable, which is indicated by the value true.

Step 8

In this step, we will retrieve the solution and fill our small grids. To achieve this, we will go back to the app.js file and define a function that takes two arguments, isSolvable and solution. The function will use the document.querySelectorAll() method to obtain all the input elements on the page and store them in a variable called inputs.

The function will proceed only if the isSolvable parameter is true and a solution is provided. In that case, the forEach() method will loop through each input element in the inputs array.

This function populates the input fields on the web page with the solved Sudoku grid. For every input element, the function assigns the corresponding value from the solution array to the input element's value property. This way, the solved Sudoku grid is displayed on the webpage.

The function logs the response data to the console using console.log(response.data) if the request is successful. It then calls the populateValues() function with two parameters, response.data.solvable and response.data.solution, which are properties of the response data object. If the request fails, an error is caught and logged to the console using console.error(error).

js
const puzzleBoard = document.querySelector('#puzzle');
const solveButton = document.querySelector('#solve-button');
const squares = 81;
const submission = [];
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input');
inputElement.setAttribute('type', 'number');
inputElement.setAttribute('min', 1);
inputElement.setAttribute('max', 9);
puzzleBoard.appendChild(inputElement);
}
const joinValues = () => {
const inputs = document.querySelectorAll('input');
submission.length = 0; // clear previous values
inputs.forEach(input => {
if (input.value) {
submission.push(input.value);
} else {
submission.push('.');
}
});
console.log(submission);
};
const populateValues = (isSolvable, solution) => {
const inputs = document.querysSelectorAll('input');
if (response.solvable && solution) {
inputs.forEach((input, i) => {
input.value = solution[i];
});
}
};
const solve = () => {
joinValues();
const data = submission.join('');
console.log('data', data);
const options = {
method: 'POST',
url: 'https://solve-sudoku.p.rapidapi.com/',
headers: {
'content-type': 'application/json',
'x-rapidapi-host': 'solve-sudoku.p.rapidapi.com',
'x-rapidapi-key': '6835772f3emsh8dbf271d59b19eep132ed4isndd226502a839'
},
data: {
puzzle: data
}
};
axios.request(options)
.then((response) => {
console.log(response.data);
populateValues(response.data.solvable, response.data.solution);
})
.catch((error) => {
console.error(error);
});
};
solveButton.addEventListener('click', solve);

Let's now try out the code by adding some values to the local host's grid and clicking the solve button. As a result, you will see that the solution is populated in each grid cell. The output seems to be correct.

We can easily display a message to indicate that the puzzle is not solvable. To do this, we must add an id of solution to the p tag in the index.html file.

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sudoku Solver</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="puzzle"></div>
<button id="solve-button">Solve</button>
<p id="solution"></p>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="app.js"></script>
</body>
</html>

Step 9

In this step, we will access the "app.js" file and define a constant solutionDisplay using document.querySelector() and the id of solution. If the Sudoku puzzle is solvable, the function sets an element's innerHTML property with the id solutionDisplay to This is the answer.

However, if the puzzle is not solvable, it sets the innerHTML property to This is not solvable. At this point, we have finished this task.

js
const puzzleBoard = document.querySelector('#puzzle');
const solveButton = document.querySelector('#solve-button');
const solutionDisplay = document.querySelector('#solution');
const squares = 81;
const submission = [];
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input');
inputElement.setAttribute('type', 'number');
inputElement.setAttribute('min', 1);
inputElement.setAttribute('max', 9);
puzzleBoard.appendChild(inputElement);
}
const joinValues = () => {
const inputs = document.querySelectorAll('input');
submission.length = 0; // clear previous values
inputs.forEach(input => {
if (input.value) {
submission.push(input.value);
} else {
submission.push('.');
}
});
console.log(submission);
};
const populateValues = (isSolvable, solution) => {
const inputs = document.querysSelectorAll('input');
if (response.solvable && solution) {
inputs.forEach((input, i) => {
input.value = solution[i];
});
solutionDisplay.innerHTML = 'This is the answer';
} else {
solutionDisplay.innerHTML = 'This is not solvable';
}
};
const solve = () => {
joinValues();
const data = submission.join('');
console.log('data', data);
const options = {
method: 'POST',
url: 'https://solve-sudoku.p.rapidapi.com/',
headers: {
'content-type': 'application/json',
'x-rapidapi-host': 'solve-sudoku.p.rapidapi.com',
'x-rapidapi-key': '6835772f3emsh8dbf271d59b19eep132ed4isndd226502a839'
},
data: {
puzzle: data
}
};
axios.request(options)
.then((response) => {
console.log(response.data);
populateValues(response.data.solvable, response.data.solution);
})
.catch((error) => {
console.error(error);
});
};
solveButton.addEventListener('click', solve);

Step 10

We can add some styling to enhance the Sudoku puzzle's visual appearance further. This can be achieved by writing a code that checks whether an input element is part of an "odd section" in a Sudoku puzzle based on its index (i) and assigns a CSS class to style it accordingly. For example, we can assign a colour to each odd section.

The code uses multiple OR conditions to determine whether an index i is part of an odd section in a Sudoku puzzle. Each condition corresponds to a different row, column, or block of the puzzle. If an index i meets any of these conditions, it is considered part of an odd section.

js
const puzzleBoard = document.querySelector('#puzzle');
const solveButton = document.querySelector('#solve-button');
const solutionDisplay = document.querySelector('#solution');
const squares = 81;
const submission = [];
for (let i = 0; i < squares; i++) {
const inputElement = document.createElement('input');
inputElement.setAttribute('type', 'number');
inputElement.setAttribute('min', 1);
inputElement.setAttribute('max', 9);
if ((i % 9 === 0 || i % 9 == 1 || i % 9 == 2) && i < 21 ||
(i % 9 === 6 || i % 9 == 7 || i % 9 == 8) && i < 27 ||
(i % 9 === 3 || i % 9 == 4 || i % 9 == 5) && i < 27 && i < 53 ||
(i % 9 === 0 || i % 9 == 1 || i % 9 == 2) && i < 53 ||
(i % 9 === 6 || i % 9 == 7 || i % 9 == 8) && i < 53) {
inputElement.classList.add('odd-section');
}
puzzleBoard.appendChild(inputElement);
}
const joinValues = () => {
const inputs = document.querySelectorAll('input');
submission.length = 0; // clear previous values
inputs.forEach(input => {
if (input.value) {
submission.push(input.value);
} else {
submission.push('.');
}
});
console.log(submission);
};
const populateValues = (isSolvable, solution) => {
const inputs = document.querySelectorAll('input');
if (isSolvable && solution) {
inputs.forEach((input, i) => {
input.value = solution[i];
});
solutionDisplay.innerHTML = 'This is the answer';
} else {
solutionDisplay.innerHTML = 'This is not solvable';
}
};
const solve = () => {
joinValues();
const data = submission.join('');
console.log('data', data);
const options = {
method: 'POST',
url: 'https://solve-sudoku.p.rapidapi.com/',
headers: {
'content-type': 'application/json',
'x-rapidapi-host': 'solve-sudoku.p.rapidapi.com',
'x-rapidapi-key': '6835772f3emsh8dbf271d59b19eep132ed4isndd226502a839'
},
data: {
puzzle: data
}
};
axios.request(options)
.then((response) => {
console.log(response.data);
populateValues(response.data.solvable, response.data.solution);
})
.catch((error) => {
console.error(error);
});
};
solveButton.addEventListener('click', solve);

The CSS code for styling the input elements with the 'odd-section' class must also be added to the style.css file.

css
#puzzle {
width: 450px;
height: 450px;
}
#puzzle input {
width: 50px;
height: 50px;
box-sizing: border-box;
border-spacing: 0;
border: 1px solid grey;
}
.odd-section {
background-color: lightgrey;
}

We have successfully implemented a method to solve any Sudoku puzzle using an API. Below is an example of what the resulting output would look like.

Wrap up

This guide explained how to use an API to solve Sudoku puzzles. The guide involved creating an HTML file with a Sudoku grid and a Solve button. In the JavaScript file, the code fetched the values from the grid and sent them to an API endpoint. The response data were used to solve the puzzle and populate the grid with the solution.

The guide also included adding CSS styling to the grid, such as colouring the odd sections of the puzzle. Finally, the guide concluded by displaying the completed Sudoku puzzle.

A few minor issues still need to be resolved, and we also need to build a secure mini-backend in Node.js to store your API keys. This could be followed in the next part of the guide, which can be seen via this link.