Skip to main content

Naming Conventions

Table of Contents

Folder Structure

Root Level Folders (Singular)

src/
├── modules/ // All custom modules
├── shared/ // Shared resources
├── routes/ // Root route
├── vendor/ // Third party components

Base Module Structure

src/
├── modules/
│ ├── auth/
│ │ ├── component/ // UI Components
│ │ ├── graphql/ // GraphQL operations
│ │ ├── route/ // Route configurations
│ │ ├── schema/ // Form validations
│ │ ├── service/ // API/Business logic
│ │ ├── hook/ // Custom hooks
│ │ ├── util/ // Helper functions
│ │ ├── constant/ // Constants, enums
│ │ ├── config/ // Module configuration
│ │ └── type/ // TypeScript types
│ │
│ ├── user/
│ │ ├── component/
│ │ ├── graphql/
│ │ ├── route/
│ │ ├── service/
│ │ ├── hook/
│ │ ├── util/
│ │ ├── constant/
│ │ ├── config/
│ │ └── type/
│ │
│ └── product/ // Similar structure for each module

└── shared/ // Shared across modules
├── component/ // Common components
├── util/ // Common utilities
├── hook/ // Shared hooks
├── constant/ // Global constants
├── service/ // Base services
└── type/ // Shared types

Directory Purposes

Each directory serves a specific purpose and should contain related code following these guidelines:

component/

Place here when code:

  • Renders UI elements
  • Handles component-level state
  • Contains presentation logic
// UserList.tsx - Component for displaying users
// UserForm.tsx - Form component for user data
// UserCard.tsx - Card component for user details

service/

Place here when code:

  • Makes API calls
  • Handles complex business logic
  • Manages data transformations
  • Interacts with external services
// user.service.ts
export class UserService {
async fetchUsers() { ... } // API calls
async processUserData() { ... } // Data processing
validateUserPermissions() { ... }// Business logic
}

util/

Place here when code:

  • Is pure function (same input → same output)
  • Has no side effects
  • Can be reused across components
// formatter.ts
export const formatCurrency = (amount: number) => { ... }
export const formatDate = (date: Date) => { ... }

helper/

Place here when code:

  • Helps with specific module tasks
  • Contains module-specific utility functions
  • Is not pure enough for utils
// user.helper.ts
export const enrichUserData = (user: User) => { ... }
export const calculateUserMetrics = (data: UserData) => { ... }

hook/

Place here when code:

  • Manages reusable stateful logic
  • Handles side effects
  • Provides shared behavior
// useUser.ts
export const useUser = (userId: string) => {
// State and effect management
};

constant/

Place here when defining:

  • Unchanging values
  • Enums
  • Configuration constants
// user.constant.ts
export const USER_ROLES = {
ADMIN: "admin",
USER: "user",
} as const;

config/

Place here when code:

  • Configures module behavior
  • Sets up feature flags
  • Defines module settings
// table.config.ts
export const userTableConfig = {
pageSize: 10,
sortableColumns: ["name", "email"],
};

type/

Place here when defining:

  • TypeScript interfaces
  • Type definitions
  • Type guards
// user.type.ts
export interface User {
id: string;
name: string;
}

File Naming

File Type Extensions & Organization

Component Files (.tsx)

  • Use PascalCase to match React component names
  • Component name should match its primary exported component
UserList.tsx
ProductDetail.tsx
SidebarMenu.tsx
UserList.test.tsx // Test files

Utility Files (.ts)

  • Use camelCase for utility functions
  • Name should be descriptive of functionality
  • Group related utilities in a single file
formatDate.ts
validateInput.ts
apiClient.ts

Type Files (.type.ts)

  • Use camelCase with .type.ts extension
  • Separate types by domain/feature
  • Include both interfaces and types for the domain
user.type.ts
product.type.ts
api.type.ts

Hook Files (.ts)

  • Start with 'use' prefix (React convention)
  • Use camelCase
  • Name should describe hook's purpose
useAuth.ts
useWindowSize.ts
useDebounce.ts

Schema Files (kebab-case)

Use kebab-case for all file and folder names to ensure cross-platform compatibility and URL friendliness.

product-form.schema.ts
user-profile.schema.ts
auth-validation.schema.ts

GraphQL Files (.ts)

  • Use singular form for directory and file names
  • Keep GraphQL operations in respective module directories
  • Use camelCase for GraphQL operation constants
module/
└── user/
└── graphql/
├── query/
│ ├── get-user.ts
│ └── get-profile.ts
├── mutation/
│ ├── update-user.ts
│ └── delete-user.ts
└── fragment/
└── user-field.ts
// graphql/query/get-user.ts
export const getUser = `
query GetUser($id: ID!) {
user(id: $id) {
...UserField
}
}
`

// graphql/fragment/user-field.ts
export const userField = `
fragment UserField on User {
id
name
email
}
`

// graphql/mutation/update-user.ts
export const updateUser = `
mutation UpdateUser($id: ID!, $input: UserInput!) {
updateUser(id: $id, input: $input) {
...UserField
}
}
`

Route Naming

URL Structure

  • Always use plural form in URLs for resource collections
  • Maintain plural form throughout the resource path hierarchy
// Correct URL Structure
/users // List users
/users/$userId // User details
/users/$userId/settings // User settings
/users/create // Create user

Route Variable Names

Route variable names should reflect the purpose of the route while following the URL structure:

// List route: /users
export const usersListRoute = createRoute({
path: "/users",
component: UsersList,
});

// Detail route: /users/$userId
export const userDetailRoute = createRoute({
path: "/users/$userId",
component: UserDetail,
});

// Settings route: /users/$userId/settings
export const userSettingsRoute = createRoute({
path: "/users/$userId/settings",
component: UserSettings,
});

// Create route: /users/create
export const userCreateRoute = createRoute({
path: "/users/create",
component: UserCreate,
});

// Combined routes export
export const userRoutes = privateLayoutRoute.addChildren([
usersListRoute,
userDetailRoute,
userSettingsRoute,
userCreateRoute,
]);

Combined Route Exports

  • Always use plural (it's a collection)
export const userRoutes = privateLayoutRoute.addChildren([
usersListRoute,
userProfileRoute,
userSettingsRoute,
]);

URL Paths:

  • Always plural (/users, /products, /posts)
  • Consistent through the entire path
  • Use kebab-case for multi-word resources (/blog-posts)

Route Variable Names:

  • Use camelCase
  • Name reflects the route's purpose
  • Format: {resource}{Action}Route
  • Examples:
    • usersListRoute for listing
    • userDetailRoute for details
    • userCreateRoute for creation
    • userEditRoute for editing

Examples for Different Resources

Posts

// URLs              Route Names
/posts postsListRoute
/posts/$postId postDetailRoute
/posts/$postId/edit postEditRoute
/posts/create postCreateRoute

Products

// URLs                  Route Names
/products productsListRoute
/products/$productId productDetailRoute
/products/$productId/edit productEditRoute
/products/create productCreateRoute

Schema Organizations

Simple Forms (1-3 fields)

Keep schemas focused and single-purpose:

// schema/login-form.schema.ts
import { z } from "zod";
import { commonValidationSchema } from "@/lib/schemas/common-validation.schema";

export const loginFormSchema = z.object({
email: commonValidationSchema.email,
password: commonValidationSchema.password,
});

export type LoginFormData = z.infer<typeof loginFormSchema>;

Complex Forms (4+ fields)

Group related schemas with clear sections:

// schema/user-form.schema.ts
import { z } from "zod";

// Basic Information
export const userBasicSchema = z.object({
firstName: z.string().min(2),
lastName: z.string().min(2),
email: z.string().email(),
phone: z.string().optional(),
});

// Address Information
export const userAddressSchema = z.object({
street: z.string(),
city: z.string(),
state: z.string(),
zipCode: z.string(),
});

// Combined schema
export const userCompleteSchema = userBasicSchema.merge(userAddressSchema);

// Type exports
export type UserBasicData = z.infer<typeof userBasicSchema>;
export type UserAddressData = z.infer<typeof userAddressSchema>;
export type UserCompleteData = z.infer<typeof userCompleteSchema>;

Why Combined Schemas?

  1. Full Form Validation

    • Validate entire form data at once
    • Ensure all required fields are present
    • Maintain data consistency
  2. Complete Type Safety

    • Get TypeScript types for the entire form
    • Ensure all data is properly typed
    • Prevent missing fields
  3. API Submissions

    • Validate complete payload before submission
    • Ensure data integrity
    • Maintain consistent data structure

Best Practices

Do's

  • Keep folder names singular
  • Use descriptive, purpose-indicating names
  • Maintain consistent casing per file type
  • Group related files together
  • Use appropriate file extensions (.ts, .tsx)

Don'ts

  • Mix naming conventions within same type
  • Use unclear abbreviations
  • Create deeply nested folder structures
  • Use special characters in names
  • Mix plural and singular forms