Expert guidance for building maintainable Node.js/TypeScript backends with PostgreSQL, emphasizing functional programming, type safety, SQL optimization, and comprehensive testing practices.
Expert guidance for writing highly maintainable and efficient backend code using TypeScript, Node.js, and PostgreSQL with a functional programming approach.
Follow these principles when writing backend TypeScript code:
1. **Use functional programming patterns** - Avoid classes and unnecessary use of `this`
2. **Modularize code** - Break down into reusable, modular functions grouped logically
3. **Use descriptive camelCase naming** - Examples: `createFlow`, `flowExists`, `isProjectUser`
4. **Write pure functions** where possible, especially for utility logic
5. **Export explicitly** - Group exports logically at the end of files
6. **Annotate types explicitly** - Include parameter and return types for all functions
7. **Prevent `this` usage** - Use `this: void` in function signatures when needed
1. **Prefer `interface` over `type`** for object shapes
2. **Avoid `any`** - Strive for precise, explicit type definitions
3. **Use `async`/`await`** for all asynchronous operations
4. **Leverage type inference judiciously** - Be explicit when it improves readability
5. **Use PascalCase** for interfaces and types
6. **Prefix booleans** with `is`, `has`, `should`, or `can`
1. **Create custom error classes** for specific conditions (e.g., `FlowDoesNotExistError`)
2. **Provide meaningful error messages** with relevant context (IDs, parameters)
3. **Validate inputs early** - Check at the beginning of functions and throw errors immediately
4. **Use early returns** for invalid inputs or error conditions
5. **Test error cases** - Use `await expect(...).rejects.toThrow(ErrorClass)` in Jest
6. **Avoid exposing sensitive information** in error messages
1. **Always use parameterized queries** - Never concatenate user input into SQL
2. **Write SQL using tagged template literals** with your SQL handler
3. **Use CTEs (WITH clauses)** for complex queries to enhance readability
4. **Use transactions** (`begin`, `commit`, `rollback`) for multi-step operations
5. **Ensure atomic operations** to maintain data integrity
6. **Handle optional parameters carefully** to avoid SQL syntax errors
7. **Optimize queries** - Use appropriate indexes and avoid unnecessary data retrieval
8. **Batch operations** when possible to reduce overhead
Example SQL with CTE:
```typescript
const result = await sql`
WITH user_projects AS (
SELECT project_id
FROM project_members
WHERE user_id = ${userId}
)
SELECT * FROM flows
WHERE project_id IN (SELECT project_id FROM user_projects)
`;
```
1. **Use Jest framework** - Organize with `describe` and `it` blocks
2. **Write comprehensive tests** covering all public functions, edge cases, and errors
3. **Isolate units** - Use mocks to isolate the code under test
4. **Ensure deterministic tests** - Tests should be independent of external state
5. **Use appropriate matchers** - `toEqual`, `toThrow`, `rejects.toThrow`, etc.
6. **Test error conditions** explicitly
Example test structure:
```typescript
describe('createFlow', () => {
it('should create a flow successfully', async () => {
const result = await createFlow({ name: 'test', projectId: 1 });
expect(result).toEqual(expect.objectContaining({ name: 'test' }));
});
it('should throw FlowExistsError when flow already exists', async () => {
await expect(createFlow({ name: 'duplicate', projectId: 1 }))
.rejects.toThrow(FlowExistsError);
});
});
```
1. **Use consistent indentation** (2 spaces recommended)
2. **Use single quotes** for strings; template literals when interpolating
3. **Use destructuring** for objects and arrays
4. **Use default parameters** for optional function arguments
5. **Use short-circuit evaluation** for defaults and conditionals
1. **Write JSDoc comments** for all public functions and interfaces
2. **Document parameters and return types** clearly
3. **Use inline comments sparingly** - Only for complex logic
4. **Keep comments updated** with code changes
Example JSDoc:
```typescript
/**
* Creates a new flow in the specified project.
* @param {Object} params - Flow creation parameters
* @param {string} params.name - Name of the flow
* @param {number} params.projectId - ID of the parent project
* @returns {Promise<Flow>} The created flow object
* @throws {FlowExistsError} If a flow with the same name already exists
*/
async function createFlow(params: CreateFlowParams): Promise<Flow> {
// Implementation
}
```
1. **Sanitize and validate all user inputs** before processing
2. **Follow secure coding practices** - Prevent SQL injection, XSS, etc.
3. **Verify permissions** before performing operations
4. **Handle sensitive data carefully** - Never log or expose it
After making code changes:
1. **Run linting**: `yarn lint` in the module directory
2. **Run tests**: `yarn test` to verify all tests pass
3. **Review for style compliance** and best practices
4. **Integrate into CI/CD** to catch issues early
1. **Requirement Analysis** - Clearly define what each function should accomplish
2. **Design** - Outline function signatures, types, and data flow
3. **Implementation** - Write code following these guidelines
4. **Testing** - Develop comprehensive tests covering all cases
5. **Linting and Testing** - Run `yarn lint` and `yarn test`
6. **Review** - Evaluate for compliance and correctness
7. **Documentation** - Document for future maintenance
```typescript
interface CreateFlowParams {
name: string;
projectId: number;
description?: string;
}
interface Flow {
id: number;
name: string;
projectId: number;
description: string | null;
createdAt: Date;
}
class FlowExistsError extends Error {
constructor(name: string, projectId: number) {
super(`Flow "${name}" already exists in project ${projectId}`);
this.name = 'FlowExistsError';
}
}
/**
* Creates a new flow in the database.
*/
async function createFlow(
this: void,
params: CreateFlowParams
): Promise<Flow> {
const { name, projectId, description = null } = params;
// Validate inputs
if (!name || !projectId) {
throw new Error('Name and projectId are required');
}
// Check if flow exists
const existing = await sql<Flow[]>`
SELECT * FROM flows
WHERE name = ${name} AND project_id = ${projectId}
`;
if (existing.length > 0) {
throw new FlowExistsError(name, projectId);
}
// Insert new flow
const [flow] = await sql<Flow[]>`
INSERT INTO flows (name, project_id, description)
VALUES (${name}, ${projectId}, ${description})
RETURNING *
`;
return flow;
}
export { createFlow, type CreateFlowParams, type Flow, FlowExistsError };
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/backend-typescript-development-expert/raw