Skip to main content
The Core template provides a production-ready monorepo architecture for building full-stack ERN (Express-React-Node) applications. This guide walks you through building a complete weather application using the Core template.

What You’ll Build

We’ll examine the pre-built Weather API that demonstrates:
  • RESTful API endpoints with Express
  • Type-safe controllers and services
  • React frontend with modern UI components
  • Full-stack TypeScript integration
  • Cluster-based backend for performance

Project Structure

The Core template organizes your application into clear, maintainable packages:
packages/core/
├── source/
   ├── Server/          # Backend Express application
   ├── src/
   ├── routes/        # API route definitions
   ├── controller/    # Request handlers
   ├── services/      # Business logic
   ├── types/         # TypeScript types
   ├── cluster/       # Node cluster management
   └── config/        # Configuration
   └── package.json
   └── frontend/        # React application
       ├── src/
   ├── pages/         # Route pages
   ├── components/    # React components
   ├── api/           # API client
   ├── hooks/         # Custom hooks
   └── config/        # Frontend config
       └── package.json
└── package.json

Understanding the Architecture

Backend Flow

The backend follows a layered architecture pattern:
1

Routes

Routes define API endpoints and map them to controllers.Example: packages/core/source/Server/src/routes/weather.routes.ts:1
import { Router } from 'express';
import { WeatherController } from '../controller/weather.controller';

const router = Router();

router.get('/location', WeatherController.getWeatherByLocation);
router.get('/coordinates', WeatherController.getWeatherByCoordinates);

export default router;
2

Controllers

Controllers handle HTTP requests, validate input, and call services.Example: packages/core/source/Server/src/controller/weather.controller.ts:8
export class WeatherController {
  static async getWeatherByLocation(req: Request, res: Response): Promise<void> {
    try {
      const { location } = req.query;

      if (!location || typeof location !== 'string') {
        res.status(400).json({
          error: 'Bad Request',
          message: 'Location parameter is required',
        });
        return;
      }

      const weatherData = await WeatherService.getWeatherByLocation(location);
      res.json(weatherData);
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      res.status(500).json({
        error: 'Internal Server Error',
        message: errorMessage,
      });
    }
  }
}
3

Services

Services contain business logic and external API integrations.Example: packages/core/source/Server/src/services/weather.service.ts:10
export class WeatherService {
  static async getWeatherByLocation(location: string): Promise<WeatherData> {
    try {
      const response = await axios.get(
        `${WEATHER_CONFIG.BASE_URL}/${encodeURIComponent(location)}`,
        {
          params: { format: 'j1' },
          headers: { 'User-Agent': 'TailStack Weather App' },
        }
      );
      
      // Transform API response to our WeatherData format
      const current = response.data.current_condition[0];
      const locationData = response.data.nearest_area[0];
      
      return {
        location: {
          name: locationData.areaName[0].value,
          region: locationData.region[0].value,
          country: locationData.country[0].value,
          lat: parseFloat(locationData.latitude),
          lon: parseFloat(locationData.longitude),
        },
        current: {
          temp_c: parseFloat(current.temp_C),
          temp_f: parseFloat(current.temp_F),
          humidity: parseFloat(current.humidity),
          // ... more fields
        },
      };
    } catch (error) {
      throw new Error('Failed to fetch weather data');
    }
  }
}

Frontend Flow

The frontend uses a modern React architecture:
1

API Layer

Type-safe API client for backend communication.Example: packages/core/source/frontend/src/api/weather.api.ts:12
import axios from 'axios';
import { API_CONFIG } from '@/config/api';

const apiClient = axios.create({
  baseURL: API_CONFIG.baseURL,
  headers: { 'Content-Type': 'application/json' },
});

export const weatherApi = {
  getWeatherByLocation: async (location: string): Promise<WeatherData> => {
    const response = await apiClient.get<WeatherData>(
      API_CONFIG.endpoints.weather.byLocation,
      { params: { location } }
    );
    return response.data;
  },
};
2

Custom Hooks

Hooks encapsulate state management and API calls.Location: packages/core/source/frontend/src/hooks/use-weather.ts
export function useWeather() {
  const [weather, setWeather] = useState<WeatherData | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  
  const handleSearch = async () => {
    setLoading(true);
    setError(null);
    try {
      const data = await weatherApi.getWeatherByLocation(location);
      setWeather(data);
    } catch (err) {
      setError('Failed to fetch weather data');
    } finally {
      setLoading(false);
    }
  };
  
  return { weather, loading, error, handleSearch };
}
3

Pages & Components

Pages compose UI components with business logic.Example: packages/core/source/frontend/src/pages/weather.tsx:21
export function WeatherPage() {
  const {
    location,
    setLocation,
    weather,
    loading,
    error,
    handleSearch
  } = useWeather();
  
  return (
    <div className="container py-10">
      <Card>
        <CardHeader>
          <CardTitle>Search Weather</CardTitle>
        </CardHeader>
        <CardContent>
          <Input
            value={location}
            onChange={(e) => setLocation(e.target.value)}
            onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
          />
          <Button onClick={handleSearch} disabled={loading}>
            Search
          </Button>
        </CardContent>
      </Card>
      {weather && <WeatherDisplay data={weather} />}
    </div>
  );
}

Running Your Application

Start both frontend and backend in development mode:
# From the core package directory
pnpm dev
This runs:
  • Frontend: http://localhost:5173 (Vite dev server)
  • Backend: http://localhost:5000 (Express with hot reload)
The dev script uses concurrently to run both:
{
  "scripts": {
    "dev": "concurrently \"pnpm --filter ./source/frontend dev\" \"pnpm --filter ./source/Server dev\""
  }
}

Environment Configuration

Backend Environment

Create packages/core/source/Server/.env:
# Server Configuration
PORT=5000
NODE_ENV=development

# CORS Configuration
CORS_ORIGIN=http://localhost:5173

# Cluster Configuration
WORKERS=0  # 0 = auto-detect CPU cores
The backend uses a cluster system for production performance. Set WORKERS=0 to auto-detect CPU cores, or specify a number for manual control.

Frontend Environment

Create packages/core/source/frontend/.env:
# API Configuration
VITE_API_BASE_URL=http://localhost:5000
The frontend reads this in src/config/api.ts:2:
export const API_CONFIG = {
  baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000',
  endpoints: {
    weather: {
      byLocation: '/api/weather/location',
      byCoordinates: '/api/weather/coordinates',
    },
  },
};

Type Safety

The Core template uses shared TypeScript types between frontend and backend:
// Shared type definition
export interface WeatherData {
  location: {
    name: string;
    region: string;
    country: string;
    lat: number;
    lon: number;
    localtime: string;
  };
  current: {
    temp_c: number;
    temp_f: number;
    condition: {
      text: string;
      icon: string;
    };
    humidity: number;
    wind_kph: number;
    wind_dir: string;
    pressure_mb: number;
    feelslike_c: number;
    feelslike_f: number;
    uv: number;
    vis_km: number;
  };
}

Next Steps

Frontend Development

Learn how to add new pages, components, and routing

Backend Development

Create new API routes, controllers, and services

Deployment

Deploy your application to production

Architecture

Deep dive into the monorepo architecture

Build docs developers (and LLMs) love