Building Full - Stack Applications with Create React App, Express, and TypeScript

In the modern web development landscape, creating full - stack applications that are both performant and maintainable is a top priority. Create React App (CRA), Express, and TypeScript are three powerful tools that, when combined, can help developers achieve this goal. Create React App is a popular tool for quickly setting up a React application with zero configuration. It comes with built - in support for features like hot reloading, linting, and a development server. Express is a minimal and flexible Node.js web application framework that provides a robust set of features for building servers and APIs. TypeScript, on the other hand, is a superset of JavaScript that adds static typing to the language, making code more predictable and easier to refactor. In this blog post, we will explore how to integrate these three technologies to build a full - stack application, covering fundamental concepts, usage methods, common practices, and best practices.

Table of Contents

  1. Fundamental Concepts
  2. Setting up the Project
  3. Usage Methods
  4. Common Practices
  5. Best Practices
  6. Conclusion
  7. References

Fundamental Concepts

Create React App

Create React App abstracts away the complexity of configuring a React development environment. It uses tools like Webpack for bundling, Babel for transpiling, and ESLint for linting. When you run npx create - react - app my - app, it creates a directory structure with all the necessary files and dependencies for a basic React application.

Express

Express is a lightweight web application framework for Node.js. It allows you to create servers, handle HTTP requests (GET, POST, PUT, DELETE, etc.), and manage middleware. Middleware functions in Express are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request - response cycle.

TypeScript

TypeScript adds static types to JavaScript. This means that you can define the types of variables, function parameters, and return values. For example, instead of just having a variable let num, you can have let num: number = 10. Static typing helps catch errors early in the development process and makes the codebase more self - documenting.

Setting up the Project

Step 1: Create a React App with TypeScript

First, create a new React application with TypeScript support:

npx create-react-app my-app --template typescript
cd my-app

Step 2: Set up the Express Server

Create a new directory named server in the root of your project. Navigate to this directory and initialize a new Node.js project:

mkdir server
cd server
npm init -y

Install Express and TypeScript dependencies:

npm install express
npm install --save-dev typescript @types/express @types/node

Create a tsconfig.json file in the server directory with the following basic configuration:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Create a src directory in the server directory and inside it, create a index.ts file:

import express from 'express';

const app = express();
const port = 3001;

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

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

Step 3: Configure the Build Scripts

In the package.json file of the server directory, add the following scripts:

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Step 4: Proxy Requests from React to Express

In the package.json file of the React application (in the root directory), add the following line to proxy requests to the Express server:

{
  "proxy": "http://localhost:3001"
}

Usage Methods

Making API Calls from React to Express

In your React components, you can use the fetch API or a library like axios to make requests to the Express server. Here is an example of using fetch in a TypeScript React component:

import React, { useEffect, useState } from 'react';

const App: React.FC = () => {
  const [data, setData] = useState<string | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('/');
        const text = await response.text();
        setData(text);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {data ? <p>{data}</p> : <p>Loading...</p>}
    </div>
  );
};

export default App;

Handling POST Requests in Express

In the index.ts file of the Express server, you can handle POST requests. First, you need to parse the incoming JSON data using express.json() middleware:

import express from 'express';

const app = express();
const port = 3001;

app.use(express.json());

app.post('/data', (req, res) => {
  const data = req.body;
  console.log('Received data:', data);
  res.send('Data received successfully');
});

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

Common Practices

Error Handling

In Express, it’s important to handle errors properly. You can create an error - handling middleware function:

app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
  console.error('Error:', err);
  res.status(500).send('Internal Server Error');
});

In React, you can use try...catch blocks when making API calls to handle errors gracefully.

Environment Variables

Use environment variables to store sensitive information like API keys, database connection strings, etc. In the Express server, you can use the dotenv package:

npm install dotenv

Create a .env file in the server directory and add your variables:

API_KEY=your_api_key

In your index.ts file, load the environment variables:

import dotenv from 'dotenv';
dotenv.config();

const apiKey = process.env.API_KEY;

Best Practices

Code Splitting

In React, use code splitting techniques to reduce the initial bundle size. React.lazy and Suspense are great features for this purpose. For example:

const LazyComponent = React.lazy(() => import('./LazyComponent'));

const App: React.FC = () => {
  return (
    <div>
      <React.Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </React.Suspense>
    </div>
  );
};

Use Interfaces in TypeScript

In both the React and Express parts of your application, use interfaces to define the structure of data. For example, if you have a User object in your application:

interface User {
  id: number;
  name: string;
  email: string;
}

You can then use this interface when defining function parameters or return values.

Testing

Write unit tests for both the React components and the Express routes. For React, you can use Jest and React Testing Library. For Express, you can use tools like supertest to test your API endpoints.

Conclusion

Combining Create React App, Express, and TypeScript is a powerful way to build full - stack applications. Create React App simplifies the front - end development process, Express provides a flexible server - side framework, and TypeScript adds type safety to the entire codebase. By following the concepts, usage methods, common practices, and best practices outlined in this blog post, you can create robust and maintainable full - stack applications.

References