Skip to content

Import Organization

Quick Reference

  • When to use: Organizing imports in any TypeScript file
  • Enforcement: Required - ESLint local-rules/import-order + dprint sort
  • Impact if violated: Medium - confusion and merge conflicts

The Rule

Module System

Use ES modules (import/export) exclusively. Never use CommonJS (require).

Mantle is ESM-only. All packages output ESM. All Lambda bundles are ESM (.mjs).

Import Order (Strict)

Imports must follow this exact order with no blank lines between groups:

  1. Node built-in modules (node:crypto, node:path)
  2. Third-party library imports (npm packages: aws-lambda, @middy/core)
  3. @mantleframework/* package imports (@mantleframework/env, @mantleframework/core, @mantleframework/database)
  4. @mantleframework/database/orm imports (Drizzle re-exports: eq, and, sql)
  5. Type imports (import type {...})
  6. Local/relative imports (./utils, ../shared)

IMPORTANT: No blank lines between import statements. Keep all imports as a single contiguous block.

Import Style

  • Destructure imports when possible
  • Group related imports from same module
  • Sort alphabetically within each group (dprint handles this with sortNamedImports: "caseInsensitive")
  • Use import type for TypeScript-only imports

Examples

Correct - Lambda Function

typescript
import type {APIGatewayProxyResult, Context} from 'aws-lambda'
import {getRequiredEnv} from '@mantleframework/env'
import {getDrizzleClient, withQueryMetrics} from '@mantleframework/database'
import {eq, and} from '@mantleframework/database/orm'
import {AppError} from '@mantleframework/errors'
import {logger} from '@mantleframework/observability'
import type {UserProfile} from '../entities/schema'
import {UserQueries} from '../entities/queries/userQueries'

Correct - Package Source

typescript
import {randomUUID} from 'node:crypto'
import {Tracer} from '@aws-lambda-powertools/tracer'
import {getRequiredEnv} from '@mantleframework/env'
import type {TracerConfig} from './types'

Incorrect

typescript
// Wrong - blank lines between imports
import {getRequiredEnv} from '@mantleframework/env'

import {logger} from '@mantleframework/observability'

// Wrong - CommonJS syntax
const {readFile} = require('fs/promises')

// Wrong - Direct third-party imports in handlers (violates vendor encapsulation)
import {DynamoDBClient} from '@aws-sdk/client-dynamodb'
import {drizzle} from 'drizzle-orm/postgres-js'

// Wrong - Mixed import and import type
import {AppError, type ErrorCode} from '@mantleframework/errors'
// Should separate: import {AppError} and import type {ErrorCode}

@mantleframework/* Import Convention

The Mantle framework has specific import conventions for database access:

Import FromWhatExample
@mantleframework/databaseFramework APIgetDrizzleClient, withQueryMetrics, RequiresTable, DatabaseOperation
@mantleframework/database/ormDrizzle re-exports (operators, types)eq, and, sql, SelectModel, InsertModel
drizzle-orm/pg-coreSchema definitions onlypgTable, column types (text, uuid, timestamp)
typescript
// Correct - database imports follow the convention
import {getDrizzleClient, withQueryMetrics, RequiresTable} from '@mantleframework/database'
import {eq, and, sql} from '@mantleframework/database/orm'
import type {InferSelectModel} from '@mantleframework/database/orm'
import {pgTable, text, uuid, timestamp} from 'drizzle-orm/pg-core'

Import Styles

Destructured Imports (Preferred)

typescript
// Good - destructured, specific
import {getRequiredEnv, getOptionalEnv} from '@mantleframework/env'
import {AppError, ValidationError} from '@mantleframework/errors'

Type Imports

typescript
// Good - explicit type imports
import type {APIGatewayProxyResult} from 'aws-lambda'
import type {SelectModel, InsertModel} from '@mantleframework/database/orm'

Namespace Imports (Avoid)

typescript
// Avoid - prevents tree shaking
import * as env from '@mantleframework/env'

// Prefer - specific imports
import {getRequiredEnv} from '@mantleframework/env'

ES Modules vs CommonJS

Always Use ES Modules

typescript
// ESM (correct)
import {readFile} from 'node:fs/promises'
export const myFunction = () => {}
export default MyClass

// CommonJS (never)
const {readFile} = require('fs/promises')
module.exports.myFunction = () => {}

Why ES Modules?

  1. Static analysis - Tools analyze imports at build time
  2. Tree shaking - Unused exports eliminated by esbuild
  3. Type safety - Better TypeScript integration
  4. Standard - ES modules are the JavaScript standard
  5. Node 24 - Full ESM support without flags

Enforcement

Automated

  • ESLint local-rules/import-order - Validates import ordering
  • dprint importDeclaration.sortNamedImports: "caseInsensitive" - Sorts named imports alphabetically
  • dprint module.sortImportDeclarations: "maintain" - Preserves manual group ordering

Code Review Checklist

  • [ ] ES modules syntax used (import/export)
  • [ ] Imports follow strict order
  • [ ] NO blank lines between import statements
  • [ ] Destructured where possible
  • [ ] Type imports use import type
  • [ ] No direct AWS SDK or Drizzle imports in handlers
  • [ ] @mantleframework/database/orm used for Drizzle operators
  • [ ] No circular dependencies

Consistent import organization reduces cognitive load and makes dependencies clear. Follow this pattern strictly for maintainable code.