Modern web development thrives on seamless integration and efficient workflows. This post demonstrates how to combine an Express.js API, Netlify's powerful hosting and serverless functions, robust CI/CD practices, and a modern state management solution using TypeScript.
Architecture at a Glance
Our architecture features a frontend (e.g., React/Vite) hosted on Netlify, consuming data from a TypeScript Express API. This Express API is deployed as Netlify Functions, leveraging Netlify's built-in CI/CD for automated deployments. Client-side state is managed efficiently using Zustand.
Express API with TypeScript
Express.js provides a robust and flexible foundation for building APIs. We'll create a simple Express application that can then be deployed as a Netlify Function. Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
First, set up your Express project:
// api/server.ts
import express from 'express';
import cors from 'cors';
const app = express();
app.use(cors()); // Enable CORS for frontend access
app.use(express.json());
// A simple API endpoint
app.get('/api/greeting', (req, res) => {
res.json({ message: 'Hello from Express API!', timestamp: new Date() });
});
// Export the app for serverless deployment
export default app;
Frontend State Management with Zustand
For the frontend, we'll use React (or any framework) and Zustand for state management. Zustand is a small, fast, and scalable bear-necessities state-management solution using simplified flux principles, making it ideal for modern React applications.
Define a simple store to hold our API data:
// src/store/useGreetingStore.ts
import { create } from 'zustand';
interface GreetingState {
message: string;
timestamp: string | null;
fetchGreeting: () => Promise<void>;
}
export const useGreetingStore = create<GreetingState>((set) => ({
message: 'Loading...',
timestamp: null,
fetchGreeting: async () => {
try {
const response = await fetch('/.netlify/functions/api/greeting'); // Relative path to Netlify Function
const data = await response.json();
set({ message: data.message, timestamp: data.timestamp });
} catch (error) {
console.error('Failed to fetch greeting:', error);
set({ message: 'Error fetching greeting.', timestamp: null });
}
},
}));
Consume this store in a React component:
// src/components/GreetingDisplay.tsx
import React, { useEffect } from 'react';
import { useGreetingStore } from '../store/useGreetingStore';
const GreetingDisplay: React.FC = () => {
const { message, timestamp, fetchGreeting } = useGreetingStore();
useEffect(() => {
fetchGreeting();
}, [fetchGreeting]);
return (
<div>
<h2>API Greeting:</h2>
<p>{message}</p>
{timestamp && <p>Timestamp: {new Date(timestamp).toLocaleString()}</p>}
</div>
);
};
export default GreetingDisplay;
Netlify Integration: Hosting & Serverless Functions
Netlify provides an excellent platform for hosting single-page applications and deploying serverless functions. We'll deploy our frontend and wrap our Express API into a Netlify Function. Netlify Functions allow you to run backend code without managing servers, integrating seamlessly with your frontend deployment.
First, configure your netlify.toml:
# netlify.toml
[build]
command = "npm run build" # Your frontend build command
publish = "dist" # Your frontend build output directory
[functions]
directory = "netlify/functions" # Where your Netlify Functions reside
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/api/:splat"
status = 200
Next, create the Netlify Function handler for your Express app:
// netlify/functions/api.ts
import serverless from 'serverless-http';
import app from '../../api/server'; // Adjust path to your Express app
// This wraps your Express app to make it compatible with Netlify Functions
export const handler = serverless(app);
Ensure you install serverless-http (npm install serverless-http). This package allows existing Node.js HTTP servers (like Express) to run in a serverless environment.
CI/CD & DevOps with Netlify
Netlify offers powerful, built-in CI/CD capabilities that streamline your deployment process. By connecting your GitHub, GitLab, or Bitbucket repository, every push to your main branch automatically triggers a build and deployment. Netlify CI/CD automates the entire deployment pipeline, from code commit to global CDN delivery.
- Connect Repository: Link your project's Git repository to Netlify.
- Build Settings: Netlify automatically detects
netlify.tomlfor build commands and publish directories. - Environment Variables: Manage sensitive API keys and configuration via Netlify's UI for secure CI/CD.
Any commit to your configured branch will initiate a build, deploy your frontend, and publish your serverless functions, providing a robust DevOps pipeline with minimal configuration.
Conclusion
Integrating Express, Netlify, CI/CD, and modern state management like Zustand with TypeScript creates a powerful, scalable, and maintainable full-stack application. This approach simplifies deployment, enhances developer experience, and leverages serverless architecture for cost-effectiveness and performance. By understanding how these pieces fit together, you can build sophisticated web applications with confidence and agility.