A Step-by-Step Guide to Building a Full-Stack TypeScript Weather App with Node.js, React, and Tailwind CSS

In this guide, I will walk you through building a full-stack TypeScript weather app using Node.js, React, and Tailwind CSS. This app will fetch weather data from a third-party API, display the current weather, and provide a forecast for the next five days.

Prerequisites:

Before we begin, make sure you have the following installed on your system:

  1. Node.js and npm (latest LTS version recommended)

  2. A code editor like Visual Studio Code

  3. A basic understanding of TypeScript, React, and Node.js

Step 1: Set up the project structure

First, let's create a new folder for our project and navigate into it:

mkdir weather-app
cd weather-app

Next, create a client folder for the React app and a server folder for the Node.js server:

mkdir client server

Step 2: Set up the Node.js server

Navigate to the server folder and initialize a new Node.js project with TypeScript:

cd server
npm init -y
npm install typescript ts-node @types/node --save-dev
npx tsc --init

Edit the tsconfig.json file to enable the "esModuleInterop" option:

{
  "compilerOptions": {
    "esModuleInterop": true,
    ...
  }
}

Install Express, Axios, and their respective types:

npm install express axios
npm install @types/express @types/axios --save-dev

Create a file named index.ts in the server folder and set up a basic Express server:

import express from 'express';

const app = express();
const PORT = process.env.PORT || 3001;

app.get('/', (req, res) => {
  res.send('Hello from the weather app server!');
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

Add a start script to the server/package.json file:

"scripts": {
  "start": "ts-node index.ts"
}

Test the server by running npm start. You should see the message "Server is running on port 3001".

Step 3: Set up the React app

Navigate to the client folder and create a new React app using the create-react-app template:

cd ../client
npx create-react-app . --template typescript

Install the necessary dependencies:

npm install axios react-query tailwindcss@latest postcss@latest autoprefixer@latest

Initialize Tailwind CSS:

npx tailwindcss init

Add the following to your tailwind.config.js file:

module.exports = {
  purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
  darkMode: false,
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

Configure PostCSS by creating a postcss.config.js file in the client folder:

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Import the Tailwind CSS styles in the src/index.css file:

@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

Step 4: Fetch and display weather data

Now, let's fetch the weather data from a third-party API, such as OpenWeatherMap. Sign up for a free account and get your API key.

In the server/index.ts file, add an endpoint to fetch weather data using Axios:

import axios from 'axios';

// Replace YOUR_API_KEY with your actual API key from OpenWeatherMap
const API_KEY = 'YOUR_API_KEY';

app.get('/weather/:city', async (req, res) => {
  const city = req.params.city;

  try {
    const response = await axios.get(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
    );
    res.json(response.data);
  } catch (error) {
    res.status(500).send('Error fetching weather data');
  }
});

In the client/src folder, create a new file named useWeather.ts to fetch weather data using React Query:

import { useQuery } from 'react-query';
import axios from 'axios';

const fetchWeather = async (city: string) => {
  const response = await axios.get(`/weather/${city}`);
  return response.data;
};

export const useWeather = (city: string) => {
  return useQuery(['weather', city], () => fetchWeather(city));
};

Create a new WeatherCard.tsx component to display the weather data:

import React from 'react';

interface WeatherCardProps {
  weatherData: any;
}

const WeatherCard: React.FC<WeatherCardProps> = ({ weatherData }) => {
  return (
    <div className="bg-white p-6 rounded-md shadow-md">
      <h2 className="text-xl font-bold">
        {weatherData.name}, {weatherData.sys.country}
      </h2>
      <p className="text-gray-600">{weatherData.weather[0].description}</p>
      <p className="text-4xl font-bold">
        {Math.round(weatherData.main.temp)}°C
      </p>
    </div>
  );
};

export default WeatherCard;

Now, update the src/App.tsx file to fetch and display the weather data using the useWeather hook and WeatherCard component:

import React, { useState } from 'react';
import './App.css';
import WeatherCard from './WeatherCard';
import { useWeather } from './useWeather';

function App() {
  const [city, setCity] = useState('London');
  const { data, isLoading, isError } = useWeather(city);

  const handleSearch = () => {
    // Add your search logic here
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>Weather App</h1>
        <input
          type="text"
          placeholder="Search by city"
          value={city}
          onChange={(e) => setCity(e.target.value)}
        />
        <button onClick={handleSearch}>Search</button>
        {isLoading && <p>Loading...</p>}
        {isError && <p>Error fetching weather data</p>}
        {data && <WeatherCard weatherData={data} />}
      </header>
    </div>
  );
}

export default App;

You now have a basic full-stack TypeScript weather app using Node.js, React, and Tailwind CSS. You can expand this app by adding a five-day forecast, error handling, and user input validation.

What would you add or change? Sign off in the comments!