Skip to content

Instantly share code, notes, and snippets.

@lemyskaman
Last active August 17, 2025 15:08
Show Gist options
  • Select an option

  • Save lemyskaman/ddb1de4af45ff6c342910c64eba031ce to your computer and use it in GitHub Desktop.

Select an option

Save lemyskaman/ddb1de4af45ff6c342910c64eba031ce to your computer and use it in GitHub Desktop.
Lemyskaman Nodejs Coding Style Guide

Node.js JavaScript / TypeScript Coding Style Guide

Version 1.1 – Last updated 2025-08-08

gist: lemyskaman/TS-JS-nodejs-react-vite-style-guide.md

A comprehensive coding style guide for full-stack Node.js applications with React frontend, TypeScript, and Vite compiler. This guide extends the base Node.js standards to include modern full-stack development patterns.


Table of Contents

  1. Enhanced Project Structure (Full-Stack Monorepo)
  2. React + TypeScript Guidelines
  3. Vite Configuration & Optimization
  4. Shared Component Libraries
  5. State Management Patterns
  6. API Integration & Data Flow
  7. Full-Stack Development Tooling
  8. Testing Strategy (Full-Stack)
  9. Build & Deployment Pipeline
  10. Performance Optimization (Full-Stack)

Enhanced Project Structure (Full-Stack Monorepo)

Recommended Full-Stack Monorepo Structure1234

Based on modern best practices for React + Node.js full-stack applications, here's the optimal project organization:

project-root/
├── apps/                         # Applications
│   ├── web/                      # React web app (Vite)
│   │   ├── public/
│   │   │   ├── index.html
│   │   │   └── favicon.ico
│   │   ├── src/
│   │   │   ├── app/              # App-level setup
│   │   │   │   ├── App.tsx
│   │   │   │   ├── main.tsx
│   │   │   │   └── router.tsx
│   │   │   ├── features/         # Feature-based organization
│   │   │   │   ├── auth/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── LoginForm/
│   │   │   │   │   │   │   ├── LoginForm.tsx
│   │   │   │   │   │   │   ├── LoginForm.test.tsx
│   │   │   │   │   │   │   └── LoginForm.module.css
│   │   │   │   │   │   └── SignupForm/
│   │   │   │   │   ├── hooks/
│   │   │   │   │   │   ├── useAuth.ts
│   │   │   │   │   │   └── useLogin.ts
│   │   │   │   │   ├── services/
│   │   │   │   │   │   └── authApi.ts
│   │   │   │   │   ├── store/
│   │   │   │   │   │   └── authSlice.ts
│   │   │   │   │   ├── types/
│   │   │   │   │   │   └── auth.types.ts
│   │   │   │   │   └── pages/
│   │   │   │   │       ├── LoginPage.tsx
│   │   │   │   │       └── SignupPage.tsx
│   │   │   │   ├── user-management/
│   │   │   │   └── dashboard/
│   │   │   ├── shared/           # Shared frontend concerns
│   │   │   │   ├── components/   # Reusable UI components
│   │   │   │   │   ├── ui/        # Basic UI elements
│   │   │   │   │   │   ├── Button/
│   │   │   │   │   │   ├── Input/
│   │   │   │   │   │   ├── Modal/
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── layout/
│   │   │   │   │   │   ├── Header/
│   │   │   │   │   │   ├── Sidebar/
│   │   │   │   │   │   └── Footer/
│   │   │   │   │   └── forms/
│   │   │   │   ├── hooks/        # Shared custom hooks
│   │   │   │   ├── services/     # API clients, utilities
│   │   │   │   ├── store/        # Global state management
│   │   │   │   ├── types/        # Shared TypeScript types
│   │   │   │   ├── utils/        # Helper functions
│   │   │   │   └── styles/       # Global styles & themes
│   │   │   ├── assets/           # Static assets
│   │   │   └── __tests__/        # Global test utilities
│   │   ├── vite.config.ts
│   │   ├── tsconfig.json
│   │   ├── package.json
│   │   └── .env.example
│   │
│   └── api/                      # Node.js backend
│       ├── src/
│       │   ├── features/         # Vertical slice architecture
│       │   ├── shared/
│       │   ├── types/
│       │   ├── config/
│       │   └── app.js
│       └── ...
│
├── packages/                     # Shared packages
│   ├── shared-types/            # Shared TypeScript definitions
│   ├── ui-components/           # Shared React component library
│   └── eslint-config/           # Shared ESLint configuration
│
├── tools/                       # Development & build tools
│   ├── scripts/
│   └── generators/
│
├── docs/                        # Documentation
├── .github/                     # GitHub workflows
├── package.json                 # Root package.json (workspaces)
├── tsconfig.base.json          # Base TypeScript config
├── docker-compose.yml          # Development environment
└── README.md

Key Principles

  1. Monorepo Organization: Use npm/yarn workspaces for dependency management24
  2. Feature-First Structure: Both frontend and backend organized by business features56
  3. Shared Packages: Common types, components, and configurations78
  4. Tool Separation: Different tools for different concerns (Vite for frontend, Node.js for backend)910

React + TypeScript Guidelines

Component Structure & Naming1112

// ✅ Good - Functional component with TypeScript
import React from 'react';
import styles from './UserCard.module.css';

interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
}

interface UserCardProps {
  user: User;
  onEdit?: (userId: string) => void;
  isEditable?: boolean;
  className?: string;
}

export const UserCard: React.FC<UserCardProps> = ({ 
  user, 
  onEdit, 
  isEditable = false,
  className 
}) => {
  const handleEditClick = () => {
    onEdit?.(user.id);
  };

  return (
    <div className={`${styles.userCard} ${className || ''}`}>
      <img 
        src={user.avatar || '/default-avatar.png'} 
        alt={`${user.name}'s avatar`}
        className={styles.avatar}
      />
      <div className={styles.info}>
        <h3 className={styles.name}>{user.name}</h3>
        <p className={styles.email}>{user.email}</p>
        {isEditable && (
          <button onClick={handleEditClick} className={styles.editButton}>
            Edit
          </button>
        )}
      </div>
    </div>
  );
};

export default UserCard;

React Hooks Best Practices

// ✅ Good - Custom hook with TypeScript
import { useState, useEffect, useCallback } from 'react';

interface UseApiOptions {
  immediate?: boolean;
}

interface UseApiReturn<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
  refetch: () => Promise<void>;
}

export const useApi = <T>(
  apiCall: () => Promise<T>,
  options: UseApiOptions = {}
): UseApiReturn<T> => {
  const { immediate = true } = options;
  
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  const fetchData = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      const result = await apiCall();
      setData(result);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An error occurred');
    } finally {
      setLoading(false);
    }
  }, [apiCall]);

  useEffect(() => {
    if (immediate) {
      fetchData();
    }
  }, [fetchData, immediate]);

  return {
    data,
    loading,
    error,
    refetch: fetchData
  };
};

React Component Organization

Component Type Location Naming Convention Example
Feature Components features/[feature]/components/ PascalCase UserProfile.tsx
Shared UI Components shared/components/ui/ PascalCase Button.tsx
Layout Components shared/components/layout/ PascalCase Header.tsx
Page Components features/[feature]/pages/ PascalCase + "Page" UserListPage.tsx
Hook Files hooks/ or features/[feature]/hooks/ camelCase + "use" prefix useAuth.ts

Vite Configuration & Optimization

Essential Vite Configuration

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { loadEnv } from 'vite';

export default defineConfig(({ command, mode }) => {
  // Load environment variables
  const env = loadEnv(mode, process.cwd(), '');
  
  return {
    plugins: [
      react({
        // Enable React Fast Refresh
        fastRefresh: true,
        // JSX runtime configuration
        jsxRuntime: 'automatic'
      })
    ],
    
    // Path resolution
    resolve: {
      alias: {
        '@': resolve(__dirname, 'src'),
        '@/components': resolve(__dirname, 'src/shared/components'),
        '@/hooks': resolve(__dirname, 'src/shared/hooks'),
        '@/utils': resolve(__dirname, 'src/shared/utils'),
        '@/types': resolve(__dirname, 'src/shared/types'),
        '@/assets': resolve(__dirname, 'src/assets')
      }
    },

    // Development server configuration
    server: {
      port: 3000,
      open: true,
      proxy: {
        '/api': {
          target: env.VITE_API_URL || 'http://localhost:8000',
          changeOrigin: true,
          secure: false
        }
      }
    },

    // Build optimizations
    build: {
      outDir: 'dist',
      sourcemap: mode !== 'production',
      minify: 'terser',
      terserOptions: {
        compress: {
          drop_console: mode === 'production'
        }
      },
      rollupOptions: {
        output: {
          manualChunks: {
            vendor: ['react', 'react-dom'],
            router: ['react-router-dom'],
            ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu']
          }
        }
      },
      chunkSizeWarningLimit: 1000
    },

    // Environment variables prefix
    envPrefix: 'VITE_',

    // CSS configuration
    css: {
      modules: {
        localsConvention: 'camelCase'
      },
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@/styles/variables.scss";`
        }
      }
    },

    // Testing configuration (Vitest)
    test: {
      environment: 'jsdom',
      setupFiles: ['./src/__tests__/setup.ts'],
      globals: true,
      css: true
    }
  };
});

TypeScript Configuration for Vite

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    
    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    
    // Linting
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    
    // Path mapping (matches Vite aliases)
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@/components/*": ["src/shared/components/*"],
      "@/hooks/*": ["src/shared/hooks/*"],
      "@/utils/*": ["src/shared/utils/*"],
      "@/types/*": ["src/shared/types/*"],
      "@/assets/*": ["src/assets/*"]
    }
  },
  "include": ["src/**/*", "src/**/*.tsx", "vite-env.d.ts"],
  "exclude": ["node_modules", "dist"]
}

Shared Component Libraries

Component Library Structure87

// packages/ui-components/src/components/Button/Button.tsx
import React from 'react';
import styles from './Button.module.css';

export interface ButtonProps 
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  icon?: React.ReactNode;
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ 
    className, 
    variant = 'primary', 
    size = 'md', 
    loading, 
    icon, 
    children, 
    disabled, 
    ...props 
  }, ref) => {
    const classes = [
      styles.button,
      styles[variant],
      styles[size],
      className
    ].filter(Boolean).join(' ');

    return (
      <button
        ref={ref}
        className={classes}
        disabled={disabled || loading}
        {...props}
      >
        {loading && <span className={styles.spinner} />}
        {icon && <span className={styles.icon}>{icon}</span>}
        {children}
      </button>
    );
  }
);

Button.displayName = 'Button';

Storybook Configuration8

// packages/ui-components/.storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-a11y',
    '@storybook/addon-design-tokens'
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {}
  },
  viteFinal: (config) => {
    return config;
  }
};

export default config;

State Management Patterns

Redux Toolkit Setup

// apps/web/src/shared/store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { authSlice } from '@/features/auth/store/authSlice';
import { userSlice } from '@/features/user-management/store/userSlice';

export const store = configureStore({
  reducer: {
    auth: authSlice.reducer,
    users: userSlice.reducer
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST']
      }
    }),
  devTools: process.env.NODE_ENV !== 'production'
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

React Query Alternative

// apps/web/src/shared/hooks/useQueryClient.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

export const useUsers = () => {
  return useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => res.json()),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
};

export const useCreateUser = () => {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: (userData: CreateUserRequest) => 
      fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(userData)
      }).then(res => res.json()),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });
};

API Integration & Data Flow

HTTP Client Setup

// apps/web/src/shared/services/httpClient.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

class HttpClient {
  private instance: AxiosInstance;

  constructor() {
    this.instance = axios.create({
      baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000',
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json'
      }
    });

    this.setupInterceptors();
  }

  private setupInterceptors() {
    // Request interceptor - Add auth token
    this.instance.interceptors.request.use(
      (config) => {
        const token = localStorage.getItem('token');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      (error) => Promise.reject(error)
    );

    // Response interceptor - Handle errors
    this.instance.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response?.status === 401) {
          localStorage.removeItem('token');
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );
  }

  async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.get(url, config);
    return response.data;
  }

  async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.post(url, data, config);
    return response.data;
  }

  async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.put(url, data, config);
    return response.data;
  }

  async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.delete(url, config);
    return response.data;
  }
}

export const httpClient = new HttpClient();

Full-Stack Development Tooling

Package.json Scripts (Root)

{
  "name": "fullstack-app",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "dev": "concurrently \"npm run dev:api\" \"npm run dev:web\"",
    "dev:api": "npm run dev --workspace=apps/api",
    "dev:web": "npm run dev --workspace=apps/web",
    "build": "npm run build --workspaces",
    "test": "npm run test --workspaces",
    "test:e2e": "playwright test",
    "lint": "npm run lint --workspaces",
    "lint:fix": "npm run lint:fix --workspaces",
    "type-check": "npm run type-check --workspaces",
    "storybook": "npm run storybook --workspace=packages/ui-components",
    "clean": "npm run clean --workspaces && rm -rf node_modules"
  },
  "devDependencies": {
    "concurrently": "^8.2.2",
    "@playwright/test": "^1.40.0",
    "husky": "^8.0.3",
    "lint-staged": "^15.2.0"
  }
}

Testing Strategy (Full-Stack)

Frontend Testing Setup13

// apps/web/src/__tests__/setup.ts
import '@testing-library/jest-dom';
import { expect, afterEach, vi } from 'vitest';
import { cleanup } from '@testing-library/react';
import * as matchers from '@testing-library/jest-dom/matchers';

expect.extend(matchers);

// Cleanup after each test
afterEach(() => {
  cleanup();
});

// Mock fetch
global.fetch = vi.fn();

// Mock localStorage
const localStorageMock = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
};
vi.stubGlobal('localStorage', localStorageMock);

Component Testing Example

// apps/web/src/shared/components/ui/Button/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders correctly', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
  });

  it('calls onClick when clicked', () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('shows loading state', () => {
    render(<Button loading>Loading</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });

  it('applies correct variant classes', () => {
    render(<Button variant="secondary">Secondary</Button>);
    expect(screen.getByRole('button')).toHaveClass('secondary');
  });
});

Build & Deployment Pipeline

Docker Configuration

# apps/web/Dockerfile
FROM node:18-alpine as build

WORKDIR /app

# Copy package files
COPY package*.json ./
COPY apps/web/package*.json ./apps/web/
COPY packages/*/package*.json ./packages/

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY apps/web ./apps/web
COPY packages ./packages

# Build the application
WORKDIR /app/apps/web
RUN npm run build

# Production image
FROM nginx:alpine

# Copy built assets
COPY --from=build /app/apps/web/dist /usr/share/nginx/html

# Copy nginx configuration
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Performance Optimization (Full-Stack)

React Performance Patterns

// Lazy loading components
import { lazy, Suspense } from 'react';

const UserManagementPage = lazy(() => 
  import('../features/user-management/pages/UserManagementPage')
);

const AppRoutes = () => (
  <Routes>
    <Route 
      path="/users" 
      element={
        <Suspense fallback={<div>Loading...</div>}>
          <UserManagementPage />
        </Suspense>
      } 
    />
  </Routes>
);

// Memoization for expensive components
import { memo, useMemo } from 'react';

interface UserListProps {
  users: User[];
  searchTerm: string;
}

export const UserList = memo<UserListProps>(({ users, searchTerm }) => {
  const filteredUsers = useMemo(() => 
    users.filter(user => 
      user.name.toLowerCase().includes(searchTerm.toLowerCase())
    ),
    [users, searchTerm]
  );

  return (
    <div>
      {filteredUsers.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
});

Vite Bundle Analysis

// vite.config.ts - Bundle analysis
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    // ... other plugins
    visualizer({
      filename: 'dist/stats.html',
      open: true
    })
  ],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Separate vendor chunks
          'react-vendor': ['react', 'react-dom'],
          'router-vendor': ['react-router-dom'],
          // Feature-based chunks
          'auth-feature': ['./src/features/auth'],
          'user-feature': ['./src/features/user-management']
        }
      }
    }
  }
});

Summary

This enhanced full-stack coding style guide provides a comprehensive foundation for building modern, scalable Node.js applications with React frontends. The combination of:

  • Vertical Slice Architecture on the backend
  • Feature-based organization on the frontend65
  • Monorepo structure for code sharing342
  • TypeScript throughout the stack1211
  • Vite for optimal development experience141516

Creates a robust development environment that scales with team size and application complexity.

Key Takeaways17109

  1. Consistency Across Stack: Use TypeScript and consistent patterns in both frontend and backend
  2. Feature-First Organization: Structure both React and Node.js code around business features
  3. Shared Code: Leverage monorepo benefits for shared types, components, and utilities78
  4. Developer Experience: Vite + TypeScript + Hot reload = fast development cycles
  5. Production Ready: Include testing, linting, building, and deployment considerations from day one

Remember to adapt these guidelines to your specific project needs while maintaining the core principles of maintainability, scalability, and developer productivity.

Footnotes

  1. https://dev.to/shubhadip_bhowmik/best-folder-structure-for-react-complex-projects-432p

  2. https://www.dhiwise.com/post/best-practices-for-structuring-your-react-monorepo 2 3

  3. https://mayallo.com/nx-monorepo-nodejs-react/ 2

  4. https://dev.to/hardikidea/master-full-stack-monorepos-a-step-by-step-guide-2196 2 3

  5. https://www.robinwieruch.de/react-folder-structure/ 2

  6. https://www.thatsoftwaredude.com/content/14110/creating-a-good-folder-structure-for-your-vite-app 2

  7. https://dev.to/ricardo_maia_eb9c7a906560/sharing-components-micro-frontends-2p61 2 3

  8. https://dzone.com/articles/component-library-with-lerna-monorepo-vite-and-sto 2 3 4

  9. https://www.linkedin.com/pulse/best-practices-combining-reactjs-nodejs-modern-web-applications-4yswf 2

  10. https://dev.to/shanu001x/how-to-setup-full-stack-project-for-production-in-nodejs-environment-2d7l 2

  11. https://react-typescript-style-guide.com 2

  12. https://mkosir.github.io/typescript-style-guide/ 2

  13. https://dev.to/janoskocs/setting-up-a-react-project-using-vite-typescript-vitest-2gl2

  14. https://www.robinwieruch.de/vite-typescript/

  15. https://javascript.plainenglish.io/setting-up-a-react-typescript-project-with-vite-eslint-prettier-and-husky-ef7c9dada761

  16. https://vite.dev/config/

  17. https://jurnal.itscience.org/index.php/brilliance/article/view/5971

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment