Best practices
Following these best practices helps maintain code quality, consistency, and security across the lomi. project.
Code style
Maintain clean, readable code. We use Prettier and ESLint for automatic formatting and linting (bun run lint:fix
).
1. Formatting and Readability
// Good: Readable, consistent formatting, meaningful names
function isValidAmount(amount: number, maxAmount: number = 1000000): boolean {
return amount > 0 && amount <= maxAmount;
}
// Bad: Poor formatting, short unclear names, magic numbers
# function validate(a){return a>0&&a<=1000000}
Code standards
Maintain high code quality by following these standards:
1. TypeScript
Use TypeScript for type safety and clarity.
// Good: Clear interface and types
interface PaymentRequest {
amount: number; // Amount in smallest currency unit (e.g., cents)
currency: string; // ISO 4217 currency code (e.g., 'XOF')
providerCode: string; // Unique identifier for the payment provider
}
async function processPayment(request: PaymentRequest): Promise<PaymentResult> {
// Implementation...
// Use types for return values as well
}
Security
Security is paramount. Follow these guidelines to protect user data and system integrity.
1. Environment variables
Never commit sensitive keys or secrets directly into the codebase. Use environment variables.
# .env.example - Provide a template for required variables
LOMI_API_KEY=
LOMI_WEBHOOK_SECRET=
NODE_ENV=development
# .gitignore - Ensure local environment files are not tracked
.env
.env.local
*.log
Refer to the API Environment documentation for managing API keys.
2. Sensitive data handling
Avoid logging sensitive information like API keys, passwords, or personal user data. If necessary, ensure it’s properly redacted.
// Example: Redact sensitive information in logs
const sanitizeData = (data: any) => {
const masked = { ...data };
// Define sensitive keys specific to your context
const sensitiveKeys = ['apiKey', 'secretKey', 'password', 'authorization', 'phoneNumber'];
sensitiveKeys.forEach(key => {
if (masked[key]) masked[key] = '[REDACTED]';
});
return masked;
};
Always validate and sanitize user input to prevent injection attacks. Consider practices like Webhook Signature Verification to ensure data integrity.
Error Handling
Handle errors gracefully and provide context.
1. Graceful Handling
// Good: Catch specific errors, provide context, log appropriately
import { logger } from './logger'; // Assuming a logger utility
try {
const result = await processPayment(request);
logger.info({ transactionId: result.id }, 'Payment successful');
} catch (error) {
if (error instanceof ValidationError) {
logger.warn({ error, request }, 'Invalid payment request');
// Return specific error response to client
} else if (error instanceof PaymentProviderError) {
logger.error({ error, request }, 'Payment provider failed');
// Handle provider-specific failure (e.g., retry logic, alert)
} else {
logger.error({ error, request }, 'Unexpected error during payment processing');
// Handle unknown error
}
}
# Bad: Ignores error, logs vaguely
/*
try {
await processPayment(request);
} catch (error) {
console.error("Something went wrong"); // Lacks context
}
*/
Testing
Write meaningful tests using Vitest (describe
, it
, expect
). Aim for comprehensive unit, integration, and end-to-end tests.
import { describe, it, expect } from 'vitest';
import { processPayment } from '../src/payment-processor'; // Adjust path
describe('Payment Processing', () => {
it('should process a valid payment request', async () => {
const request: PaymentRequest = {
amount: 10000, // e.g., 100.00 XOF
currency: 'XOF',
providerCode: 'PROVIDER_X',
};
// Mock dependencies if necessary
// vi.mock(...)
const result = await processPayment(request);
expect(result).toBeDefined();
expect(result.status).toBe('completed');
// Add more specific assertions
});
it('should throw validation error for invalid amount', async () => {
const request: PaymentRequest = {
amount: -500,
currency: 'XOF',
providerCode: 'PROVIDER_X',
};
await expect(processPayment(request)).rejects.toThrow(ValidationError);
});
});
Git workflow
Adhere to our established Git workflow for smooth collaboration.
1. Branch management
Keep feature branches focused on a single task and relatively short-lived. Regularly rebase with the develop
branch.
# Create a focused feature branch
git checkout develop
git pull upstream develop
git checkout -b feature/add-wave-provider
# Work on the feature...
# Commit changes following guidelines
git commit -m "feat(payments): implement Wave provider"
# Push the branch
git push origin feature/add-wave-provider
See the full Branching Strategy for details.
2. Commit messages
Use conventional commit messages to provide clarity and enable automated changelog generation.
# Good: Specific type, scope, and description
git commit -m "feat(auth): implement API key generation endpoint"
git commit -m "fix(webhooks): handle duplicate event processing"
git commit -m "docs(contributing): clarify rebase workflow"
# Bad: Vague, uninformative
# git commit -m "fixed stuff"
# git commit -m "wip"
# git commit -m "more changes"
Documentation
Good documentation is essential for maintainability and collaboration.
1. Code comments
Use comments to explain why something is done, not what it does (the code should explain the what). Use TSDoc for functions and complex logic.
/**
* Processes a payment request using the specified provider.
* Handles potential errors and ensures idempotency.
* @param request - The payment request details.
* @returns A promise resolving to the payment result.
* @throws {ValidationError} If the request payload is invalid.
* @throws {PaymentProcessingError} If the provider fails.
* @example
* ```typescript
* const result = await processPayment({ amount: 5000, currency: 'XOF', providerCode: 'WAVE' });
* console.log(result.transactionId);
* ```
*/
async function processPayment(
request: PaymentRequest
): Promise<PaymentResult> {
// Implementation details...
}
2. README files
Ensure packages and significant components have README
files explaining their purpose, usage, configuration, and how to run tests.
# Package/Component Name
## Overview
A brief description of what this package/component does.
## Installation
`pnpm add @lomi/package-name`
## Usage
Provide clear code examples and instructions.
## Configuration
Detail any available configuration options.
## Testing
Explain how to run tests for this specific package/component.
`pnpm --filter @lomi/package-name test`
Deployment
Follow standard procedures for deployment.
1. CI/CD
Continuous Integration and Continuous Deployment pipelines automate testing and deployment. Ensure your changes pass all checks.
# Example: .github/workflows/ci.yml (Simplified)
name: CI Checks
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8 # Specify pnpm version
- uses: actions/setup-node@v3
with:
node-version: '18' # Specify Node.js version
cache: 'pnpm'
- run: pnpm install
- run: pnpm lint
- run: pnpm test
2. Version control
Package versions are managed within their respective package.json
files and updated according to our Versioning guide.
// Example: packages/api/package.json
{
"name": "@lomi/api",
"version": "1.2.3",
"engines": {
"node": ">=18"
},
"main": "dist/index.js"
}