Working with the Lichess API
My journey into programming chess applications started with Chessboard Magic, a personal project that turned into a platform with over 34 unique chess-based applications. This blog continues my Chess Web Programming series, diving into one of the most exciting aspects of modern development: working with APIs. In this installment, we’ll explore the Lichess API, learn how to interact with it, and create a React application to fetch and display Games from any Lichess User in a structured and beginner-friendly way.
Recap of the Previous Parts
Before we dive into the Lichess API, here’s a quick recap of the earlier blogs in the series:
- Getting Started: Using
react-chessboardandchess.jsto set up a functional chessboard. - Integrating Stockfish: Enhancing the application by suggesting the best move using Stockfish.
- Deploying Your Application: A guide to hosting your chess application online.
- Customizing the Chessboard: Styling and implementing features like custom piece sets and square highlighting.
- Game Review: Adding features like move highlighting and navigation for game analysis.
- Essential Resources: Tools, libraries, and learning materials to support chess application development.
Note: This blog assumes that you’ve followed the previous parts of the series, particularly Blog 1, which covers setting up the foundational tools and concepts. You should already have the necessary software installed (e.g., Node.js, Visual Studio Code) and a basic understanding of React web programming. If you haven’t reviewed Blog 1 yet, we recommend starting there to ensure you’re set up for success.
What Are APIs?
APIs (Application Programming Interfaces) act as intermediaries that allow software applications to communicate with one another. They provide a set of rules and protocols for accessing a service or application’s features or data. For example, when you check the weather on your phone, the app fetches the data from a server using APIs. Similarly, we’ll use the Lichess API to retrieve chess game data.
What Is a RESTful API?
A RESTful API (Representational State Transfer API) is a type of API that adheres to specific architectural principles. It uses standard HTTP methods such as GET, POST, PUT, and DELETE to manage resources (like game data or user profiles).
RESTful APIs:
- Are stateless, meaning every request from the client to the server must contain all the information needed to process the request.
- Use endpoints (specific URLs) to access different resources.
For instance, the Lichess API uses endpoints to provide access to specific types of data. Here’s an example:
- Endpoint:
https://lichess.org/api/games/user/{username} - Purpose: Fetches games played by a given Lichess user.
In this blog, we’ll focus on the Games endpoint to fetch and display a user’s game history.
What Is HTTP?
HTTP (HyperText Transfer Protocol) is the foundation of communication on the web. It defines how messages are formatted and transmitted between clients (like a browser or application) and servers. For example, when your application sends a request to the Lichess API, it uses HTTP to communicate. Common HTTP methods include:
- GET: Retrieve data from a server.
- POST: Send data to a server.
- PUT: Update data on a server.
- DELETE: Remove data from a server.
The Lichess API
The Lichess API is a RESTful API that gives developers access to a wealth of chess-related data. Here are some of its features:
- User Data: Retrieve information about a specific user, such as their rating and games played.
- Game Data: Access complete game histories, including moves, openings, and final positions.
- Tournaments and Puzzles: Fetch details about tournaments or solve chess puzzles programmatically.
In this tutorial, we’ll use the Games endpoint to:
- Fetch a specified number of games for a given user.
- Include details like the opening name, moves, and final position (FEN).
By understanding how endpoints work, you’ll be able to explore other parts of the Lichess API and integrate additional features into your application in the future.
Setting Up a React Application
In this section, we’ll create a React application from scratch using Create React App. This will serve as the foundation for our Lichess API integration.
Step 1: Create a New React Application
React is a popular JavaScript library for building user interfaces. To get started, we’ll use a tool called Create React App, which sets up a new project with all the necessary configurations and dependencies pre-installed.
- Open Your Terminal
You’ll need a terminal or command-line tool to run commands. On Windows, you can use Command Prompt or PowerShell. On macOS or Linux, use the built-in terminal. - Run the Create React App Command
In your terminal, typenpx create-react-app lichess-api-app. Thenpxcommand ensures that you’re using the latest version of Create React App, whilelichess-api-appis the name of your project folder. This command downloads and installs all the dependencies required for a React project. It might take a few minutes depending on your internet connection. - Navigate Into Your Project Folder
Once the installation is complete, use thecd lichess-api-appcommand to navigate into the folder where your new React project is located. - What’s Inside the Folder?
Your project folder contains several files and directories:- A
node_modulesfolder with all the dependencies installed for your project. - A
publicfolder that includes static assets like theindex.htmlfile. - A
srcfolder, which is the main location for your React components and logic. - A
package.jsonfile, which lists project metadata and dependencies. - A
README.mdfile, which contains instructions and information about your project.
- A
At this stage, your new React application is ready, and you’re set to begin building your project.
Open the Project in VS Code
Once your React application is created, the next step is to open it in a code editor. We’ll use Visual Studio Code (VS Code), a popular editor for web development.
- Launch VS Code
Open Visual Studio Code from your operating system’s application menu. - Add the Project Folder to Workspace
In VS Code:- Click on File in the top menu bar.
- Select Add Folder to Workspace.
- In the file explorer that appears, navigate to the folder where you created your React project (
lichess-api-app). - Select the folder and click Add.
- Explore the Workspace
Once the folder is added, you’ll see the project structure in the VS Code Explorer panel on the left. Key elements include:srcfolder: This is where you’ll write your React code.publicfolder: Contains theindex.htmlfile, where your React application is injected.package.json: Lists project dependencies and configuration.
- Open the Terminal in VS Code
To run commands directly from VS Code:- Go to the top menu and select View Terminal.
- A terminal window will open at the bottom of the editor, starting in your project folder.
- Verify Everything is Set Up
In the terminal, start the development server by typingnpm start. This will run your React application and open it in your default web browser athttp://localhost:3000.
If you see the default React welcome page in your browser, everything is set up correctly.
Note: If you have another server running on your machine (for example, another React project), you may encounter a message like:
“Something is already running on port 3000.”
In this case, you will be given two options:
- Use a Different Port: React will prompt you to run the application on a different port, such as
http://localhost:3001. You can pressYto confirm.- Shut Down the Other Server: If you prefer to run your application on port 3000, you’ll need to stop the other server. To do this, go to the terminal where the other server is running and press
Ctrl + C(orCmd + Con macOS) to terminate it.
Introducing Material UI
In this section, we’ll enhance the user interface of our React application using Material UI, a popular library of pre-styled and customizable components. Material UI allows us to quickly build professional, modern-looking interfaces without having to write extensive CSS. By the end of this section, we’ll have a clean and responsive form with the following features:
- An input field for the Lichess username.
- An input field for the number of games to fetch (default set to 50).
- A button to trigger the API call and fetch the games.
This form will lay the groundwork for integrating the Lichess API in the next section.
Why Use Material UI?
Material UI (MUI) is based on Google’s Material Design principles, focusing on clean, consistent, and user-friendly interfaces. Here are some reasons to use it:
- Pre-Styled Components: Includes a library of pre-designed components like buttons, tables, and input fields.
- Customizability: You can easily tweak the styles using inline styling, theming, or CSS overrides.
- Accessibility: Material UI components are built with accessibility in mind, making your application more user-friendly.
- Ease of Use: You can achieve professional designs with minimal effort, reducing the need to write custom CSS.
By using Material UI, we can focus more on building the application logic rather than spending time on UI design from scratch.
Step 1: Install Material UI
To begin using Material UI, we need to install the required packages. These include:
[@mui](/@/mui)/material: The core Material UI component library.[@emotion](/@/emotion)/reactand[@emotion](/@/emotion)/styled: Required for styling the Material UI components.
Install these packages by running the following command in your terminal:
npm install [@mui](/@/mui)/material [@emotion](/@/emotion)/react [@emotion](/@/emotion)/styled
The [@emotion](/@/emotion)/react and [@emotion](/@/emotion)/styled packages are necessary for Material UI’s styling to work. Once installed, you can start using Material UI components in your project.
Step 2: Create the Basic Form
Now, we’ll set up a basic form in App.js with:
- An input field for the Lichess username.
- An input field for the number of games to fetch (default 50).
- A button to fetch games.
Replace the content of src/App.js with the following:
import React, { useState } from 'react';
import { TextField, Button, Container, Typography } from '[@mui](/@/mui)/material';
function App() {
const [username, setUsername] = useState('');
const [numberOfGames, setNumberOfGames] = useState(50);
const fetchGames = () => {
console.log(`Fetching ${numberOfGames} games for ${username}`);
};
return (
<Container>
<Typography variant="h4" gutterBottom>
Lichess Game Fetcher
</Typography>
<TextField
label="Lichess Username"
variant="outlined"
fullWidth
margin="normal"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<TextField
label="Number of Games"
variant="outlined"
type="number"
fullWidth
margin="normal"
value={numberOfGames}
onChange={(e) => setNumberOfGames(e.target.value)}
/>
<Button
variant="contained"
color="primary"
onClick={fetchGames}
style={{ marginTop: '20px' }}
>
Fetch Games
</Button>
</Container>
);
}
export default App;
Step 3: Understanding the Code
- Container: A layout component from Material UI that centers the content and adds padding.
- Typography: Used to style text elements like headings and paragraphs. We used it here to create a title for the application.
- TextField: A Material UI input component. We added two text fields:
- For entering the Lichess username.
- For specifying the number of games to fetch.
- Button: A Material UI button component. When clicked, it triggers the
fetchGamesfunction.
The useState hooks manage the form’s state:
usernamestores the entered Lichess username.numberOfGamesstores the number of games to fetch.
The fetchGames function logs the entered values to the console. We’ll enhance this function in the next section to fetch data from the Lichess API.
Step 4: Preview the Form
Save your changes and check the browser. You should see:
- A title labeled “Lichess Game Fetcher.”
- Two input fields for the username and the number of games.
- A button labeled “Fetch Games.”

At this stage, you’ve built a functional form using Material UI. In the next section, we’ll connect this form to the Lichess API to fetch and display game data.
Fetching and Displaying Games
In this section, we’ll integrate the Lichess API into our React application. The goal is to fetch a list of chess games based on the input values (Lichess username and number of games) and display the data in a clean and structured format using a Material UI Table. We’ll enhance the table to include player details, results, game dates, opening names, final positions, and the sequence of moves.
Step 1: Understand the Lichess API Endpoint
The Games API endpoint retrieves games played by a specific Lichess user. The structure of the endpoint is as follows:
Endpoint:https://lichess.org/api/games/user/{username}
Query parameters:
max: Specifies the maximum number of games to fetch (user input, default 50).opening: Includes the opening name in the response if set totrue.moves: Retrieves the complete move list if set totrue.lastFen: Includes the final board position in FEN format if set totrue.
Step 2: Fetch Games from the Lichess API
To retrieve games in NDJSON format:
- Build the API URL dynamically with user inputs.
- Set the
Acceptheader to request NDJSON. - Parse the NDJSON response and store it in a state variable for rendering.
Here’s the function to fetch games:
const fetchGames = async () => {
if (!username) {
alert("Please enter a Lichess username."); // Validate username input.
return;
}
const url = `https://lichess.org/api/games/user/${username}?max=${numberOfGames}&opening=true&moves=true&lastFen=true`;
try {
const response = await fetch(url, {
headers: { Accept: 'application/x-ndjson' }, // Request NDJSON format.
});
const text = await response.text(); // Read the NDJSON response as plain text.
const parsedGames = text
.trim() // Remove extra spaces or newlines.
.split('\n') // Split the NDJSON by newline to separate game data.
.map((line) => JSON.parse(line)); // Parse each line into a JSON object.
setGames(parsedGames); // Update the state with the parsed game data.
} catch (error) {
console.error("Error fetching games:", error); // Log any errors to the console.
alert("Failed to fetch games. Please try again."); // Show an error message to the user.
}
};
Explanation of code:
- Validates the input to ensure a username is provided.
- Dynamically constructs the API URL using the username and number of games, with parameters to include opening names, moves, and the final board position in FEN format.
- Sends a GET request to the Lichess API, requesting data in NDJSON format by setting the appropriate
Acceptheader. - Processes the NDJSON response by:
- Reading it as plain text.
- Splitting it into lines (one line per game).
- Parsing each line into a JSON object.
- Updates the
gamesstate with the parsed data, triggering a UI update to display the results. - Handles errors by logging them to the console and showing an alert to the user.
Step 3: Add a Table to Display Games
We’ll use Material UI’s Table component to dynamically display the game data fetched from the Lichess API. Each row will represent a single game, with the following columns:
- White: Displays the username and rating of the White player.
- Black: Displays the username and rating of the Black player.
- Result: Indicates the result of the game (e.g., “White Wins,” “Black Wins,” or “Draw”).
- Date: Shows the date when the game was played, formatted for readability.
- Opening: Displays the name of the opening used in the game, if available.
- Last Position: Shows the final board position in FEN format.
- Moves: Displays the sequence of moves played during the game.
Here’s the updated implementation of the table with inline comments explaining each part:
{games.length > 0 && ( // Check if games are available before rendering the table.
<Table sx={{ marginTop: '20px' }}> {/* Material UI Table with spacing above */}
<TableHead>
<TableRow>
<TableCell>White</TableCell> {/* Column for White player's username and rating */}
<TableCell>Black</TableCell> {/* Column for Black player's username and rating */}
<TableCell>Result</TableCell> {/* Column for game result */}
<TableCell>Date</TableCell> {/* Column for game date */}
<TableCell>Opening</TableCell> {/* Column for opening name */}
<TableCell>Last Position</TableCell> {/* Column for final board position (FEN) */}
<TableCell>Moves</TableCell> {/* Column for sequence of moves */}
</TableRow>
</TableHead>
<TableBody>
{/* Loop through the games array to generate a table row for each game */}
{games.map((game, index) => (
<TableRow key={index}> {/* Unique key for each row to optimize rendering */}
{/* White player's details */}
<TableCell>
{game.players.white.user?.name || "Anonymous"} {/* Username or fallback */}
{" "}({game.players.white.rating}) {/* Rating */}
</TableCell>
{/* Black player's details */}
<TableCell>
{game.players.black.user?.name || "Anonymous"} {/* Username or fallback */}
{" "}({game.players.black.rating}) {/* Rating */}
</TableCell>
{/* Game result */}
<TableCell>
{game.winner
? game.winner === "white"
? "White Wins" /* If winner is White */
: "Black Wins" /* If winner is Black */
: "Draw" /* If no winner */}
</TableCell>
{/* Game date */}
<TableCell>
{new Date(game.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
})} {/* Format timestamp into readable date */}
</TableCell>
{/* Opening name */}
<TableCell>
{game.opening?.name || "Unknown"} {/* Display opening name or fallback */}
</TableCell>
{/* Final board position in FEN format */}
<TableCell>
{game.lastFen} {/* Display the final board position */}
</TableCell>
{/* Moves played in the game */}
<TableCell>
{game.moves ? game.moves : "Moves not available"} {/* Display moves or fallback */}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
Explanation of the Code
games.length > 0Check:- Ensures that the table is only rendered if the
gamesarray contains data. - Prevents rendering an empty table when no games are available.
- Ensures that the table is only rendered if the
- Material UI Table Structure:
- The
<Table>component serves as the container for the table. <TableHead>defines the table header row, with<TableCell>elements for each column title.<TableBody>contains the dynamic rows generated from thegamesarray.
- The
- Looping Through the Games Array:
- The
mapmethod is used to iterate over thegamesarray. - Each iteration creates a
<TableRow>representing a single game. - Unique keys (
key={index}) are assigned to each<TableRow>for efficient rendering.
- The
- Fallback Values:
- Fallback values like
"Anonymous","Unknown", or"Moves not available"ensure the application handles missing data gracefully.
- Fallback values like
- Dynamic Data Rendering:
- Player details, results, dates, openings, FEN, and moves are extracted from the
gameobject and displayed in corresponding<TableCell>elements.
- Player details, results, dates, openings, FEN, and moves are extracted from the
- Date Formatting:
- The
createdAtfield is a timestamp in milliseconds. It’s converted to a human-readable format usingtoLocaleDateString.
- The
Here is the final code:
import React, { useState } from 'react';
import {
TextField,
Button,
Container,
Typography,
Box,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from '[@mui](/@/mui)/material';
function App() {
const [username, setUsername] = useState(''); // State for the Lichess username input.
const [numberOfGames, setNumberOfGames] = useState(50); // State for the number of games to fetch (default is 50).
const [games, setGames] = useState([]); // State to store the fetched games data.
// Function to fetch games from the Lichess API.
const fetchGames = async () => {
// Validate input: Ensure a username has been provided.
if (!username) {
alert("Please enter a Lichess username.");
return;
}
// Construct the API URL using the username and number of games.
const url = `https://lichess.org/api/games/user/${username}?max=${numberOfGames}&opening=true&moves=true&lastFen=true`;
try {
// Make the API request, specifying that we want NDJSON data.
const response = await fetch(url, {
headers: { Accept: 'application/x-ndjson' },
});
// Read the response as text and parse the NDJSON data into an array of objects.
const text = await response.text();
const parsedGames = text
.trim() // Remove any leading or trailing whitespace.
.split('\n') // Split the NDJSON response into individual lines.
.map((line) => JSON.parse(line)); // Parse each line as JSON.
// Update the state with the parsed games data.
setGames(parsedGames);
} catch (error) {
console.error("Error fetching games:", error); // Log errors to the console.
alert("Failed to fetch games. Please try again."); // Show an alert to the user.
}
};
return (
<Container>
{/* Page Title */}
<Typography variant="h4" gutterBottom>
Lichess API: Fetch Games
</Typography>
{/* Input Form */}
<Box
sx={{
display: 'flex', // Display inputs and button in a row.
alignItems: 'center', // Align items vertically.
gap: '16px', // Add spacing between elements.
marginTop: '20px', // Add spacing above the form.
}}
>
{/* Input for Lichess username */}
<TextField
label="Lichess Username"
variant="outlined"
value={username} // Bind the value to the username state.
onChange={(e) => setUsername(e.target.value)} // Update the username state on input change.
style={{ flex: 2 }} // Set the size of this input field relative to the button.
/>
{/* Input for number of games */}
<TextField
label="Number of Games"
variant="outlined"
type="number" // Restrict input to numbers.
value={numberOfGames} // Bind the value to the numberOfGames state.
onChange={(e) => setNumberOfGames(e.target.value)} // Update the numberOfGames state on input change.
style={{ flex: 1 }} // Make this input smaller than the username input.
/>
{/* Button to trigger the fetchGames function */}
<Button
variant="contained"
color="primary"
onClick={fetchGames} // Trigger the fetchGames function when clicked.
>
Fetch Games
</Button>
</Box>
{/* Table to display fetched game data */}
{games.length > 0 && ( // Render the table only if there are games to display.
<Table sx={{ marginTop: '20px' }}>
<TableHead>
<TableRow>
<TableCell>White</TableCell> {/* Column header for White player details */}
<TableCell>Black</TableCell> {/* Column header for Black player details */}
<TableCell>Result</TableCell> {/* Column header for game result */}
<TableCell>Date</TableCell> {/* Column header for game date */}
<TableCell>Opening</TableCell> {/* Column header for opening name */}
<TableCell>Last Position</TableCell> {/* Column header for FEN */}
<TableCell>Moves</TableCell> {/* Column header for game moves */}
</TableRow>
</TableHead>
<TableBody>
{/* Loop through the games array to generate a table row for each game */}
{games.map((game, index) => (
<TableRow key={index}> {/* Unique key for each row */}
{/* White player details */}
<TableCell>
{game.players.white.user?.name || "Anonymous"} {/* Username or fallback */}
{" "}({game.players.white.rating}) {/* Rating */}
</TableCell>
{/* Black player details */}
<TableCell>
{game.players.black.user?.name || "Anonymous"} {/* Username or fallback */}
{" "}({game.players.black.rating}) {/* Rating */}
</TableCell>
{/* Game result */}
<TableCell>
{game.winner
? game.winner === "white"
? "White Wins" /* If White is the winner */
: "Black Wins" /* If Black is the winner */
: "Draw" /* If no winner */}
</TableCell>
{/* Game date */}
<TableCell>
{new Date(game.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
})} {/* Convert timestamp to readable date */}
</TableCell>
{/* Opening name */}
<TableCell>
{game.opening?.name || "Unknown"} {/* Display opening name or fallback */}
</TableCell>
{/* Final board position (FEN) */}
<TableCell>
{game.lastFen} {/* Display the FEN or fallback */}
</TableCell>
{/* Moves played in the game */}
<TableCell>
{game.moves ? game.moves : "Moves not available"} {/* Display moves or fallback */}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</Container>
);
}
export default App;
When you enter a Lichess Username and click on Fetch, you should not see the following:
Formatting Our Output
In this section, we’ll take the code from Section 4 and format the output into the final design. The goal is to enhance the user experience by structuring the game data into two columns:
- Column A: Displays the final chessboard position using the
react-chessboardlibrary. - Column B: Contains detailed game information organized into multiple rows, such as player names and ratings, game results, date and time, opening name, and moves.
We’ll explain step by step how to achieve this transformation.
Step 1: Install react-chessboard
To display the chessboard for the final position, we’ll use the react-chessboard library. If you haven’t already installed it, run the following command in your project directory:
npm install react-chessboard
After installing, you can import the Chessboard component at the top of your file:
import { Chessboard } from "react-chessboard";
Step 2: Modify the Table Layout
In Section 4, the game data was displayed in a single-row table with columns for White, Black, Result, Date, Opening, Final Position, and Moves. We’ll now restructure the table:
- Combine all game details into Column B as a vertical layout.
- Use Column A to display the chessboard with the final position (
lastFen).
Key Changes:
- Replace individual
<TableCell>elements for game details with a<Box>containing rows for:- Player names and ratings.
- Game result.
- Date and time.
- Opening name.
- Moves.
Step 3: Configure the Chessboard in Column A
The react-chessboard library provides an interactive chessboard that can display a game position using the FEN format. For our use case:
- Use the
lastFenfield from the API response to set the final position. - Disable dragging of chess pieces using the
arePiecesDraggable={false}property. - Adjust the size of the chessboard to
200pxusingboardWidth.
Here’s how to integrate the chessboard into Column A:
<Chessboard
position={game.lastFen || "start"} // Use the final FEN position or fallback to the starting position.
arePiecesDraggable={false} // Disable dragging on the chessboard.
boardWidth={200} // Set the width of the chessboard.
/>
Step 4: Organize Game Details in Column B
To format the game details:
- Use a
Boxcomponent withflexDirection: "column"to organize rows vertically. - Format each piece of information (e.g., players, result, date) using
<Typography>for consistent styling. - Add spacing between rows using the
gapproperty.
<Box sx={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{/* Row 1: Players and ratings */}
<Typography variant="h6">
{game.players.white.user?.name || "Anonymous"} ({game.players.white.rating}) vs{" "}
{game.players.black.user?.name || "Anonymous"} ({game.players.black.rating})
</Typography>
{/* Row 2: Game result */}
<Typography variant="body1">
Result:{" "}
{game.winner
? game.winner === "white"
? "White Wins"
: "Black Wins"
: "Draw"}
</Typography>
{/* Row 3: Date and time played */}
<Typography variant="body2">
Date:{" "}
{new Date(game.createdAt).toLocaleString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</Typography>
{/* Row 4: Opening name */}
<Typography variant="body2">
Opening: {game.opening?.name || "Unknown"}
</Typography>
{/* Row 5: Moves played in the game */}
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}>
Moves: {game.moves || "Moves not available"}
</Typography>
</Box>
Step 5: Final Code
Here’s the final code after applying all formatting:
import React, { useState } from "react";
import {
TextField,
Button,
Container,
Typography,
Box,
Table,
TableBody,
TableCell,
TableRow,
} from "[@mui](/@/mui)/material";
import { Chessboard } from "react-chessboard"; // Import Chessboard component.
function App() {
// State for the Lichess username input.
const [username, setUsername] = useState("");
// State for the number of games to fetch (default is 50).
const [numberOfGames, setNumberOfGames] = useState(50);
// State to store the fetched games data.
const [games, setGames] = useState([]);
// Function to fetch games from the Lichess API.
const fetchGames = async () => {
// Validate input: Ensure a username has been provided.
if (!username) {
alert("Please enter a Lichess username.");
return;
}
// Construct the API URL using the username and number of games.
const url = `https://lichess.org/api/games/user/${username}?max=${numberOfGames}&opening=true&moves=true&lastFen=true`;
try {
// Make the API request, specifying that we want NDJSON data.
const response = await fetch(url, {
headers: { Accept: "application/x-ndjson" },
});
// Read the response as text and parse the NDJSON data into an array of objects.
const text = await response.text();
const parsedGames = text
.trim() // Remove any leading or trailing whitespace.
.split("\n") // Split the NDJSON response into individual lines.
.map((line) => JSON.parse(line)); // Parse each line as JSON.
// Update the state with the parsed games data.
setGames(parsedGames);
} catch (error) {
console.error("Error fetching games:", error); // Log errors to the console.
alert("Failed to fetch games. Please try again."); // Show an alert to the user.
}
};
return (
<Container>
{/* Page Title */}
<Typography variant="h4" gutterBottom>
Lichess API: Fetch Games
</Typography>
{/* Input Form */}
<Box
sx={{
display: "flex", // Display inputs and button in a row.
alignItems: "center", // Align items vertically.
gap: "16px", // Add spacing between elements.
marginTop: "20px", // Add spacing above the form.
}}
>
{/* Input for Lichess username */}
<TextField
label="Lichess Username"
variant="outlined"
value={username} // Bind the value to the username state.
onChange={(e) => setUsername(e.target.value)} // Update the username state on input change.
style={{ flex: 2 }} // Set the size of this input field relative to the button.
/>
{/* Input for number of games */}
<TextField
label="Number of Games"
variant="outlined"
type="number" // Restrict input to numbers.
value={numberOfGames} // Bind the value to the numberOfGames state.
onChange={(e) => setNumberOfGames(e.target.value)} // Update the numberOfGames state on input change.
style={{ flex: 1 }} // Make this input smaller than the username input.
/>
{/* Button to trigger the fetchGames function */}
<Button
variant="contained"
color="primary"
onClick={fetchGames} // Trigger the fetchGames function when clicked.
>
Fetch Games
</Button>
</Box>
{/* Table to display fetched game data */}
{games.length > 0 && (
<Table sx={{ marginTop: "20px" }}>
<TableBody>
{/* Loop through the games array to generate rows for each game */}
{games.map((game, index) => (
<TableRow key={index}>
{/* Column A: Chessboard */}
<TableCell>
<Chessboard
position={game.lastFen || "start"} // Use the final FEN position or fallback to the starting position.
arePiecesDraggable={false} // Disable dragging on the chessboard.
boardWidth={200} // Set the width of the chessboard.
/>
</TableCell>
{/* Column B: Game details */}
<TableCell>
<Box
sx={{
display: "flex", // Use a flex container.
flexDirection: "column", // Stack rows vertically.
gap: "8px", // Add spacing between rows.
}}
>
{/* Row 1: Players and ratings */}
<Typography variant="h6">
{game.players.white.user?.name || "Anonymous"} ({game.players.white.rating}) vs{" "}
{game.players.black.user?.name || "Anonymous"} ({game.players.black.rating})
</Typography>
{/* Row 2: Game result */}
<Typography variant="body1">
Result:{" "}
{game.winner
? game.winner === "white"
? "White Wins"
: "Black Wins"
: "Draw"}
</Typography>
{/* Row 3: Date and time played */}
<Typography variant="body2">
Date:{" "}
{new Date(game.createdAt).toLocaleString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</Typography>
{/* Row 4: Opening name */}
<Typography variant="body2">
Opening: {game.opening?.name || "Unknown"}
</Typography>
{/* Row 5: Moves played in the game */}
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}>
Moves: {game.moves || "Moves not available"}
</Typography>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</Container>
);
}
export default App;
Your final application should look like the following:
Summary
In this blog, we explored the exciting intersection of chess and web development by building a React application powered by the Lichess API. We started by understanding the basics of RESTful APIs and how they enable seamless interaction between applications. Using the Lichess API, we fetched user-specific chess games, parsed the data, and displayed it in a clean and interactive format. Along the way, we integrated Material UI for styling and react-chessboard to visually represent the final positions of games, creating an application that is both functional and visually appealing.
Our final application features a two-column layout: one column displays the final position of each game on a chessboard, while the other provides detailed information about the game, such as player names and ratings, results, openings, dates, and moves. By organizing the data in this way, we ensured a clear and user-friendly interface. We also focused on best practices, such as robust error handling and responsive design, to make the application reliable and adaptable.
This project is just the beginning of what you can create with the Lichess API. Whether you’re looking to build analysis tools, create educational apps, or experiment with interactive chessboard visualizations, the possibilities are endless. By combining your passion for chess with the skills you’ve learned here, you’re equipped to innovate and bring your ideas to life. The chessboard is your canvas—let your creativity take the lead!
Learn More
Here are the tools and resources we used in this article to build our Lichess game viewer application. Each of these is essential for recreating and understanding the project:
- Lichess API: The Lichess API is the backbone of this project, providing access to chess game data. Explore its extensive capabilities and learn how to fetch games, puzzles, and more.
- React: React is the framework we used to build this dynamic and interactive application. Learn how to create components, manage state, and build engaging user interfaces.
- Material UI: Material UI is the library we used to style the application and create components like tables, text fields, and buttons. Explore its components and theming options to enhance your React apps.
- react-chessboard: This library allowed us to display the final position of games as a static chessboard. Learn how to use it to create visually appealing chessboard components in your applications.
- Visual Studio Code: We used VS Code as our code editor throughout this project. It’s a powerful tool for coding, debugging, and managing projects. Download it to streamline your development workflow.

Leave a Reply