Build a full stack application with React and NodeJs
In this tutorial, we will create a full stack application. We will set up a client with create-react-app and a server with NodeJs and express. We will then integrate our server with our Sport winner API to get some Wimbledon winners data to provide our client with.
Sounds fun, right?
Create our server
Let's begin with our server side. We will set up our project structure and install our dependencies before we write our code.
mkdir my-sport-api cd my-sport-api npm init -y npm install axios express cors touch index.js
And in our index.js file, we will add following code to set up our small API.
const express = require("express"); const cors = require("cors"); const axios = require("axios"); const app = express(); const PORT = 3001; app.use(cors({ origin: "http://localhost:3000" })); app.get("/winners", async (req, res) => { const response = await axios.get( `https://api.algobook.info/v1/sports/wimbledon` ); res.send({ data: response.data }); }); app.get("/winners/:year", async (req, res) => { const { year } = req.params; const response = await axios.get( `https://api.algobook.info/v1/sports/wimbledon/${year}` ); res.send({ data: response.data }); }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Let's go through above code.
- We are requiring our dependencies
- We are initializing our app and specifying a port number
- We are setting up some cors rules to only allow http://localhost:3000 to access the API
- We are setting up our two endoints to get the winners of Wimbledon. One for getting all winners, and one for getting a specific winner for a year
Client side
Now, we will tie our backend together with a nice looking client. In the terminal, run following commands
npx create-react-app my-sport-client --template typescript
Then we will navigate to our App.tsx file and clear out all the code that are prefilled by the script and add our own stuff.
Let's begin with clearing out the unnecessary code and keep this:
import "./App.css"; function App() { return <div className="App"></div>; } export default App;
Create state
Let's set up our state for the App component, as well as our interfaces to specify the data we are getting from the API.
interface IPlayer { name: string; country: string; } export interface ITennisStats { year: string; setScores: string; winner: IPlayer; loser: IPlayer; } function App() { const [winners, setWinners] = useState<ITennisStats[] | null>(null); return <div className="App"></div>; }
Create fetch functions
Now, we will create two functions for calling our endpoints. One for getting all winners, and one for getting a specific year
const fetchAllWinners = async () => { const response = await fetch("http://localhost:3001/winners"); const winnerResponse = await response.json(); setWinners(winnerResponse.data); }; const fetchWinnerByYear = async (year: string | undefined) => { if (!year) { fetchAllWinners(); return; } const response = await fetch(`http://localhost:3001/winners/${year}`); const winnerResponse = await response.json(); const winner = winnerResponse.data.error ? [] : [winnerResponse.data]; setWinners(winner); };
Get default data
So, we want to present our users with some data to begin with. In order to do that, we will call our fetchAllWinners when the component is mounted using useEffect hook.
useEffect(() => { fetchAllWinners(); }, []);
App.tsx so far
Currently, our App.tsx looks like this
import { useState } from "react"; import "./App.css"; interface IPlayer { name: string; country: string; } export interface ITennisStats { year: string; setScores: string; winner: IPlayer; loser: IPlayer; } function App() { const [winners, setWinners] = useState<ITennisStats[] | null>(null); const fetchAllWinners = async () => { const response = await fetch("http://localhost:3001/winners"); const winnerResponse = await response.json(); setWinners(winnerResponse.data); }; const fetchWinnerByYear = async (year: string | undefined) => { if (!year) { fetchAllWinners(); return; } const response = await fetch(`http://localhost:3001/winners/${year}`); const winnerResponse = await response.json(); const winner = winnerResponse.data.error ? [] : [winnerResponse.data]; setWinners(winner); }; useEffect(() => { fetchAllWinners(); }, []); return <div className="App"></div>; } export default App;
Create our stats component
If you navigate to the browser, you will see a completely white page. Let's change that with a new component for displaying our data.
Create a new component called TennisStats.tsx in the src/ folder, along with a TennisStats.css file.
In our TennisStats.tsx, we will add an interface to specify the data we want our component to accept, and then we will render the data.
import { ITennisStats } from "./App"; import "./TennisStats.css"; interface ITennisStatsProps { data: ITennisStats; } export const TennisStats = (props: ITennisStatsProps) => { const { year, setScores, winner, loser } = props.data; return ( <div className="statsContainer"> <h2>{year}</h2> <div className="winner">{`Winner: ${winner.name} (${winner.country})`}</div> <div className="loser">{`Loser: ${loser.name} (${loser.country})`}</div> <div>{`Scores: ${setScores}`}</div> </div> ); };
Style the component
In our TennisStats.css file, we will create some simple styling to make it a little bit more appealing
.statsContainer { display: flex; flex-direction: column; border: 1px solid #ddd; border-radius: 4px; padding: 1rem; gap: 1rem; width: 200px; } .winner { color: #2ed22e; } .loser { color: #e33636; }
Render our component
Now it is time to actual get something on the screen. Let's create a function in App.tsx and render our stats component.
Start by importing our component in our App.tsx
import { TennisStats } from "./TennisStats";
Then, create renderTennisStats() function
const renderTennisStats = () => { if (winners?.length) { return winners.map((winner) => <TennisStats data={winner} />); } return null; };
And invoke it like this in the return clause
return <div className="App">{renderTennisStats()}</div>;
Full App.tsx code
Our full code should look like this now
import { useEffect, useState } from "react"; import "./App.css"; import { TennisStats } from "./TennisStats"; interface IPlayer { name: string; country: string; } export interface ITennisStats { year: string; setScores: string; winner: IPlayer; loser: IPlayer; } function App() { const [winners, setWinners] = useState<ITennisStats[] | null>(null); const fetchAllWinners = async () => { const response = await fetch("http://localhost:3001/winners"); const winnerResponse = await response.json(); setWinners(winnerResponse.data); }; const fetchWinnerByYear = async (year: string | undefined) => { if (!year) { fetchAllWinners(); return; } const response = await fetch(`http://localhost:3001/winners/${year}`); const winnerResponse = await response.json(); const winner = winnerResponse.data.error ? [] : [winnerResponse.data]; setWinners(winner); }; useEffect(() => { fetchAllWinners(); }, []); const renderTennisStats = () => { if (winners?.length) { return winners.map((winner) => <TennisStats data={winner} />); } return null; }; return <div className="App">{renderTennisStats()}</div>; } export default App;
App.css
Head in to App.css and remove all default styling and change the .App class to this:
.App { display: flex; flex-wrap: wrap; padding: 2rem; gap: 0.5rem; }
Result
In your browser, you should be seeing something like this:
Cool! We have now a fully functional full-stack application!
I will now continue with extending our App.tsx to make use of our second endpoint with a search input field.
Extend our App.tsx
Let's add some searching capability to our component, and let the user search for a specific year using an input. Create an input and a button in App.tsx
return ( <> <div className="inputContainer"> <input className="input" type="number" min={1968} max={2022} value={searchYear} onChange={(ev) => setSearchYear(ev.target.value)} /> <button onClick={() => fetchWinnerByYear(searchYear)}>Search</button> </div> <div className="App">{renderTennisStats()}</div> </> );
Add our searchYear state
const [searchYear, setSearchYear] = useState<string | undefined>();
Style our input
In App.css add two new classes
.inputContainer { display: flex; gap: 0.25rem; padding: 2rem; } .input { width: 200px; }
Handle bad input
If we have searched for a year that didn't result in any hits, we should tell the user so. Extend our renderTennisStats function:
const renderTennisStats = () => { if (winners?.length) { return winners.map((winner) => <TennisStats data={winner} />); } if (searchYear) { return `Nothing found for ${searchYear}`; } return null; };
Full code
And now we are done, our App.tsx should look like below code snippet
import { useEffect, useState } from "react"; import "./App.css"; import { TennisStats } from "./TennisStats"; interface IPlayer { name: string; country: string; } export interface ITennisStats { year: string; setScores: string; winner: IPlayer; loser: IPlayer; } function App() { const [winners, setWinners] = useState<ITennisStats[] | null>(null); const [searchYear, setSearchYear] = useState<string | undefined>(); const fetchAllWinners = async () => { const response = await fetch("http://localhost:3001/winners"); const winnerResponse = await response.json(); setWinners(winnerResponse.data); }; const fetchWinnerByYear = async (year: string | undefined) => { if (!year) { fetchAllWinners(); return; } const response = await fetch(`http://localhost:3001/winners/${year}`); const winnerResponse = await response.json(); const winner = winnerResponse.data.error ? [] : [winnerResponse.data]; setWinners(winner); }; useEffect(() => { fetchAllWinners(); }, []); const renderTennisStats = () => { if (winners?.length) { return winners.map((winner) => <TennisStats data={winner} />); } if (searchYear) { return `Nothing found for ${searchYear}`; } return null; }; return ( <> <div className="inputContainer"> <input className="input" type="number" min={1968} max={2022} value={searchYear} onChange={(ev) => setSearchYear(ev.target.value)} /> <button onClick={() => fetchWinnerByYear(searchYear)}>Search</button> </div> <div className="App">{renderTennisStats()}</div> </> ); } export default App;
Final result
And our final result with a search input, will look like this
Outro
Today we learned how we can built our own full stack application using React, NodeJs and even a third party API to display data for our users. We covered cors, GET endpoints, sending http requests and rendering response data in React.
I hope you liked this guide, and if you faced any issues, please don't hesitate to contact us here.
Thanks for reading!