npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@200systems/mf-db-mysql

v1.1.7

Published

MySQL database client with connection pooling, migrations, and health monitoring

Downloads

70

Readme

@200systems/mf-db-mysql

Cliente MySQL para o microframework TypeScript com connection pooling, monitoramento de saúde, gerenciamento de transações e sistema de migrações completo.

Features Implementadas

  • 🔗 Connection Pooling - Usando mysql2 com configuração avançada
  • 🏭 Singleton Factory - Gerenciamento centralizado de instâncias de clientes
  • 🔄 Transaction Management - Transações automáticas e manuais com BaseTransaction
  • 📊 Health Monitoring - Verificações de saúde com métricas de timing e pool
  • 🗃️ Migration System - Sistema completo com UP/DOWN scripts, checksum e rollback
  • ⚙️ TypeScript Support - Tipagem completa para queries e configurações
  • 🛡️ Error Handling - Tipos de erro específicos (ConnectionError, QueryError, etc.)
  • 📈 Pool Monitoring - Informações detalhadas sobre estado do pool de conexões

Instalação

npm install @200systems/mf-db-mysql

Peer Dependencies

npm install mysql2

Quick Start

1. Configuração Básica

// src/database.ts
import { MySQLFactory, MySQLConfig } from '@200systems/mf-db-mysql';
import { createLogger } from '@200systems/mf-logger';

const config: MySQLConfig = {
  host: process.env.MYSQL_HOST || 'localhost',
  port: parseInt(process.env.MYSQL_PORT || '3306'),
  database: process.env.MYSQL_DATABASE || 'myapp',
  user: process.env.MYSQL_USER || 'root',
  password: process.env.MYSQL_PASSWORD || 'secret',
  
  // Pool configuration
  max: 10,
  min: 2,
  connectionTimeoutMillis: 60000,
  idleTimeoutMillis: 30000,
  
  // MySQL specific
  charset: 'utf8mb4',
  timezone: 'Z',
  acquireTimeout: 60000,
  timeout: 60000,
  reconnect: true,
  multipleStatements: false,
  
  // SSL configuration (production)
  ssl: process.env.NODE_ENV === 'production' ? {
    rejectUnauthorized: true
  } : false
};

const logger = createLogger({ context: 'database' });
export const dbClient = MySQLFactory.getInstance(config, logger);

2. Inicialização e Queries

// src/server.ts
import { dbClient } from './database.js';

// Inicializar conexão
await dbClient.initialize();

// Query simples com placeholders MySQL (?)
const users = await dbClient.query<{ id: number; name: string; email: string }>(
  'SELECT id, name, email FROM users WHERE active = ?',
  [true]
);

console.log(`Found ${users.rowCount} users:`, users.rows);

// Cleanup gracioso
process.on('SIGTERM', async () => {
  await dbClient.close();
  process.exit(0);
});

3. Transações

// Transação automática (recomendado)
const result = await dbClient.transaction(async (trx) => {
  // Log de auditoria
  await trx.query('INSERT INTO audit_logs (action, user_id) VALUES (?, ?)', 
    ['USER_CREATED', 123]);

  // Criar usuário
  const user = await trx.query<{ id: number }>(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    ['João Silva', '[email protected]']
  );

  // Criar perfil associado
  await trx.query('INSERT INTO user_profiles (user_id, settings) VALUES (?, ?)', 
    [user.rows[0].id, '{}']);

  return user.rows[0];
});

console.log('Usuário criado:', result);

API Reference

MySQLFactory

interface MySQLConfig extends DatabaseConfig {
  // Conexão básica
  host: string;
  port: number;
  database: string;
  user: string;
  password: string;
  
  // Pool de conexões
  max?: number;                    // Máximo de conexões (padrão: 10)
  min?: number;                    // Mínimo de conexões (padrão: 2)
  connectionTimeoutMillis?: number; // Timeout de conexão (padrão: 60000)
  idleTimeoutMillis?: number;      // Timeout de idle (padrão: 30000)
  
  // MySQL específico
  charset?: string;                // Charset (padrão: 'utf8mb4')
  timezone?: string;               // Timezone (padrão: 'Z')
  acquireTimeout?: number;         // Timeout para adquirir conexão (padrão: 60000)
  timeout?: number;                // Timeout de query (padrão: 60000)
  reconnect?: boolean;             // Auto-reconnect (padrão: true)
  multipleStatements?: boolean;    // Múltiplas statements (padrão: false)
  
  // SSL (produção)
  ssl?: boolean | string | {
    rejectUnauthorized?: boolean;
    ca?: string;
    cert?: string;
    key?: string;
  };
}

// Singleton Factory
class MySQLDatabaseFactory {
  static getInstance(): MySQLDatabaseFactory;
  getInstance(config: DatabaseConfig, logger?: Logger): DatabaseClient;
  closeInstance(): Promise<void>;
}

// Instância padrão para conveniência
const MySQLFactory: MySQLDatabaseFactory;

MySQLClient

class MySQLClient implements DatabaseClient {
  // Ciclo de vida
  initialize(): Promise<void>;
  close(): Promise<void>;
  isReady(): boolean;
  
  // Queries (usando ? como placeholder - padrão MySQL)
  query<T>(sql: string, params?: any[]): Promise<QueryResult<T>>;
  transaction<T>(callback: (trx: MySQLTransaction) => Promise<T>): Promise<T>;
  
  // Monitoramento
  healthCheck(): Promise<HealthCheckResult>;
  getConnectionInfo(): { total: number; idle: number; waiting: number };
}

interface QueryResult<T> {
  rows: T[];
  rowCount: number;
  fields: Array<{ name: string; dataTypeID: number }>;
}

interface HealthCheckResult {
  status: 'healthy' | 'unhealthy';
  message: string;
  timestamp: Date;
  responseTime: number;
  details?: {
    activeConnections: number;
    totalConnections: number;
  };
}

MySQLTransaction

class MySQLTransaction extends BaseTransaction {
  query<T>(sql: string, params?: any[]): Promise<QueryResult<T>>;
  commit(): Promise<void>;
  rollback(): Promise<void>;
  isCompleted(): boolean;
}

// Uso manual (não recomendado)
const trx = await dbClient.transaction();
try {
  await trx.query('INSERT INTO logs (event) VALUES (?)', ['START']);
  await trx.query('UPDATE counters SET value = value + 1');
  await trx.commit();
} catch (error) {
  await trx.rollback();
  throw error;
}

Sistema de Migrações

MySQLMigrator

import { MySQLMigrator } from '@200systems/mf-db-mysql';

const migrator = new MySQLMigrator(
  dbClient,
  './migrations',
  logger.child('migrator')
);

// Aplicar todas as migrações pendentes
await migrator.migrate();

// Rollback da última migração
await migrator.rollback();

// Rollback até uma migração específica
await migrator.rollbackTo('20241201_001_create_users');

// Reset completo (cuidado!)
await migrator.reset();

// Verificar status
const status = await migrator.getStatus();
console.log('Migrações aplicadas:', status.map(m => m.id));

// Listar migrações pendentes
const pending = await migrator.getPending();
console.log('Pendentes:', pending.map(m => m.id));

Estrutura de Arquivos de Migração

migrations/
├── 20241201_001_create_users_table.sql
├── 20241201_002_add_user_profiles.sql
├── 20241202_001_add_indexes.sql
└── 20241203_001_add_audit_logs.sql

Formato de Arquivo de Migração

-- Criar tabela de usuários com engine InnoDB
-- UP
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_active ON users(active);

-- DOWN
DROP INDEX IF EXISTS idx_users_active ON users;
DROP INDEX IF EXISTS idx_users_email ON users;
DROP TABLE IF EXISTS users;

Regras importantes:

  • Arquivo deve ter seção -- UP e seção -- DOWN obrigatórias
  • Primeira linha com -- vira a descrição da migração
  • Arquivos aplicados em ordem lexicográfica
  • Rollback executa a seção -- DOWN em ordem reversa
  • Checksum garante integridade dos arquivos já aplicados

Exemplos de Migrações MySQL

-- migrations/20241201_001_create_users_table.sql
-- Criar tabela inicial de usuários
-- UP
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  role ENUM('admin', 'user') DEFAULT 'user',
  active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- DOWN
DROP TABLE IF EXISTS users;
-- migrations/20241201_002_add_user_profiles.sql
-- Adicionar tabela de perfis com foreign key
-- UP
CREATE TABLE user_profiles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  first_name VARCHAR(50),
  last_name VARCHAR(50),
  phone VARCHAR(20),
  avatar_url TEXT,
  settings JSON,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  UNIQUE KEY uk_user_profiles_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- DOWN
DROP TABLE IF EXISTS user_profiles;
-- migrations/20241202_001_optimize_indexes.sql
-- Otimizar índices para queries comuns
-- UP
CREATE INDEX idx_users_role_active ON users(role, active);
CREATE INDEX idx_user_profiles_name ON user_profiles(first_name, last_name);

-- Adicionar índice fulltext para busca
ALTER TABLE users ADD FULLTEXT(name, email);

-- DOWN
ALTER TABLE users DROP INDEX name;
DROP INDEX IF EXISTS idx_user_profiles_name ON user_profiles;
DROP INDEX IF EXISTS idx_users_role_active ON users;

Exemplos Práticos

Configuração com Variáveis de Ambiente

// src/config/database.ts
import { MySQLConfig } from '@200systems/mf-db-mysql';

export const dbConfig: MySQLConfig = {
  host: process.env.MYSQL_HOST || 'localhost',
  port: parseInt(process.env.MYSQL_PORT || '3306'),
  database: process.env.MYSQL_DATABASE!,
  user: process.env.MYSQL_USER!,
  password: process.env.MYSQL_PASSWORD!,
  
  // Pool otimizado para produção
  max: parseInt(process.env.DB_POOL_MAX || '10'),
  min: parseInt(process.env.DB_POOL_MIN || '2'),
  connectionTimeoutMillis: parseInt(process.env.DB_CONNECTION_TIMEOUT || '60000'),
  idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT || '30000'),
  
  // Configurações MySQL específicas
  charset: 'utf8mb4',
  timezone: 'Z',
  acquireTimeout: 60000,
  timeout: 10000,
  reconnect: true,
  multipleStatements: false,
  
  // SSL em produção
  ssl: process.env.NODE_ENV === 'production' ? {
    rejectUnauthorized: process.env.DB_SSL_REJECT_UNAUTHORIZED === 'true',
    ca: process.env.DB_SSL_CA,
    cert: process.env.DB_SSL_CERT,
    key: process.env.DB_SSL_KEY
  } : false
};

Repository Pattern

// src/repositories/UserRepository.ts
import { DatabaseClient, QueryResult } from '@200systems/mf-db-mysql';

export interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
  active: boolean;
  created_at: Date;
  updated_at: Date;
}

export class UserRepository {
  constructor(private db: DatabaseClient) {}

  async findById(id: number): Promise<User | null> {
    const result = await this.db.query<User>(
      'SELECT * FROM users WHERE id = ?',
      [id]
    );
    return result.rows[0] || null;
  }

  async findByEmail(email: string): Promise<User | null> {
    const result = await this.db.query<User>(
      'SELECT * FROM users WHERE email = ?',
      [email]
    );
    return result.rows[0] || null;
  }

  async findPaginated(limit: number, offset: number): Promise<{ users: User[]; total: number }> {
    const [usersResult, countResult] = await Promise.all([
      this.db.query<User>(
        'SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?',
        [limit, offset]
      ),
      this.db.query<{ count: number }>(
        'SELECT COUNT(*) as count FROM users'
      )
    ]);

    return {
      users: usersResult.rows,
      total: countResult.rows[0].count
    };
  }

  async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
    const result = await this.db.query<{ insertId: number }>(
      `INSERT INTO users (name, email, role, active) 
       VALUES (?, ?, ?, ?)`,
      [userData.name, userData.email, userData.role, userData.active]
    );

    return this.findById(result.rows[0].insertId)!;
  }

  async update(id: number, userData: Partial<Omit<User, 'id' | 'created_at' | 'updated_at'>>): Promise<User | null> {
    const setClause = Object.keys(userData)
      .map(key => `${key} = ?`)
      .join(', ');
    
    const values = [...Object.values(userData), id];
    
    await this.db.query(
      `UPDATE users SET ${setClause} WHERE id = ?`,
      values
    );

    return this.findById(id);
  }

  async delete(id: number): Promise<boolean> {
    const result = await this.db.query(
      'DELETE FROM users WHERE id = ?',
      [id]
    );
    return (result.rowCount || 0) > 0;
  }

  async searchByName(searchTerm: string): Promise<User[]> {
    const result = await this.db.query<User>(
      'SELECT * FROM users WHERE MATCH(name) AGAINST(? IN NATURAL LANGUAGE MODE)',
      [searchTerm]
    );
    return result.rows;
  }
}

Service com Transações

// src/services/UserService.ts
import { DatabaseClient } from '@200systems/mf-db-mysql';
import { UserRepository } from '../repositories/UserRepository.js';

export class UserService {
  constructor(
    private db: DatabaseClient,
    private userRepo: UserRepository
  ) {}

  async createUserWithProfile(userData: {
    name: string;
    email: string;
    role: 'admin' | 'user';
    firstName: string;
    lastName: string;
  }): Promise<{ user: User; profile: UserProfile }> {
    return this.db.transaction(async (trx) => {
      // Criar usuário
      const userResult = await trx.query<{ insertId: number }>(
        'INSERT INTO users (name, email, role) VALUES (?, ?, ?)',
        [userData.name, userData.email, userData.role]
      );

      const userId = userResult.rows[0].insertId;

      // Criar perfil
      await trx.query(
        `INSERT INTO user_profiles (user_id, first_name, last_name) 
         VALUES (?, ?, ?)`,
        [userId, userData.firstName, userData.lastName]
      );

      // Log de auditoria
      await trx.query(
        'INSERT INTO audit_logs (action, entity_type, entity_id, details) VALUES (?, ?, ?, ?)',
        ['CREATE', 'user', userId, JSON.stringify(userData)]
      );

      // Buscar dados criados
      const [user, profile] = await Promise.all([
        trx.query<User>('SELECT * FROM users WHERE id = ?', [userId]),
        trx.query<UserProfile>('SELECT * FROM user_profiles WHERE user_id = ?', [userId])
      ]);

      return {
        user: user.rows[0],
        profile: profile.rows[0]
      };
    });
  }

  async transferCredits(fromUserId: number, toUserId: number, amount: number): Promise<void> {
    return this.db.transaction(async (trx) => {
      // Verificar saldo suficiente
      const fromUser = await trx.query<{ credits: number }>(
        'SELECT credits FROM user_accounts WHERE user_id = ? FOR UPDATE',
        [fromUserId]
      );

      if (fromUser.rows[0].credits < amount) {
        throw new Error('Insufficient credits');
      }

      // Debitar do usuário origem
      await trx.query(
        'UPDATE user_accounts SET credits = credits - ? WHERE user_id = ?',
        [amount, fromUserId]
      );

      // Creditar para usuário destino
      await trx.query(
        'UPDATE user_accounts SET credits = credits + ? WHERE user_id = ?',
        [amount, toUserId]
      );

      // Log da transação
      await trx.query(
        `INSERT INTO credit_transactions (from_user_id, to_user_id, amount, type) 
         VALUES (?, ?, ?, ?)`,
        [fromUserId, toUserId, amount, 'transfer']
      );
    });
  }
}

Health Check e Monitoring

// src/health.ts
import { dbClient } from './database.js';

export async function checkDatabaseHealth() {
  const health = await dbClient.healthCheck();
  const connectionInfo = dbClient.getConnectionInfo();
  
  return {
    database: {
      status: health.status,
      message: health.message,
      responseTime: health.responseTime,
      timestamp: health.timestamp,
      details: health.details
    },
    connectionPool: {
      total: connectionInfo.total,
      idle: connectionInfo.idle,
      waiting: connectionInfo.waiting,
      active: connectionInfo.total - connectionInfo.idle,
      utilization: `${Math.round((connectionInfo.total - connectionInfo.idle) / connectionInfo.total * 100)}%`
    }
  };
}

// src/routes/health.ts (Express)
app.get('/health', async (req, res) => {
  try {
    const health = await checkDatabaseHealth();
    const statusCode = health.database.status === 'healthy' ? 200 : 503;
    res.status(statusCode).json(health);
  } catch (error) {
    res.status(503).json({
      database: { status: 'unhealthy', message: error.message },
      connectionPool: { total: 0, idle: 0, waiting: 0, active: 0, utilization: '0%' }
    });
  }
});

Diferenças MySQL vs PostgreSQL

Placeholders de Query

// MySQL usa ? como placeholder
await mysqlClient.query('SELECT * FROM users WHERE id = ? AND active = ?', [123, true]);

// PostgreSQL usa $1, $2, etc.
await postgresClient.query('SELECT * FROM users WHERE id = $1 AND active = $2', [123, true]);

Auto-increment vs Serial

-- MySQL
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100)
);

-- PostgreSQL  
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100)
);

Tipos de Dados

-- MySQL
JSON, ENUM('value1', 'value2'), TIMESTAMP DEFAULT CURRENT_TIMESTAMP

-- PostgreSQL
JSONB, VARCHAR(20) CHECK (column IN ('value1', 'value2')), TIMESTAMP WITH TIME ZONE

Error Handling

Tipos de Erro Disponíveis

import { 
  ConnectionError, 
  QueryError, 
  TransactionError, 
  MigrationError 
} from '@200systems/mf-db-mysql';

try {
  await dbClient.query('SELECT * FROM users');
} catch (error) {
  if (error instanceof ConnectionError) {
    console.error('Erro de conexão:', error.message);
    // Tentar reconectar ou usar fallback
  } else if (error instanceof QueryError) {
    console.error('Erro na query:', error.message);
    console.error('SQL:', error.sql);
    console.error('Params:', error.params);
    
    // Tratar erros específicos do MySQL
    if (error.message.includes('Duplicate entry')) {
      console.error('Violação de constraint UNIQUE');
    }
  } else if (error instanceof TransactionError) {
    console.error('Erro na transação:', error.message);
    // Transaction já foi automaticamente revertida
  }
}

Padrão Singleton e Múltiplos Clientes

// Múltiplos clientes para diferentes bases
const mainDbClient = MySQLFactory.getInstance({
  host: 'localhost',
  database: 'main_app',
  user: 'app_user',
  password: 'secret'
});

const analyticsDbClient = MySQLFactory.getInstance({
  host: 'analytics-server',
  database: 'analytics',
  user: 'analytics_user', 
  password: 'secret'
});

// Clients são automaticamente reutilizados se config for idêntica
const sameClient = MySQLFactory.getInstance({
  host: 'localhost',
  database: 'main_app',
  user: 'app_user',
  password: 'secret'
}); // Retorna a mesma instância de mainDbClient

// Cleanup de todas as conexões
await MySQLDatabaseFactory.getInstance().closeInstance();

Configuração de Ambiente

# .env
# Configuração do MySQL
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=microframework_app
MYSQL_USER=root
MYSQL_PASSWORD=your_password

# SSL (produção)
DB_SSL=true
DB_SSL_REJECT_UNAUTHORIZED=true
DB_SSL_CA=/path/to/ca.pem
DB_SSL_CERT=/path/to/cert.pem
DB_SSL_KEY=/path/to/key.pem

# Pool de Conexões
DB_POOL_MAX=10
DB_POOL_MIN=2
DB_CONNECTION_TIMEOUT=60000
DB_IDLE_TIMEOUT=30000

# Configuração da Aplicação
NODE_ENV=production
LOG_LEVEL=info

Migration CLI Script

// scripts/migrate.ts
import { dbClient } from '../src/database.js';
import { MySQLMigrator } from '@200systems/mf-db-mysql';
import { createLogger } from '@200systems/mf-logger';

const logger = createLogger({ context: 'migration-cli' });
const migrator = new MySQLMigrator(dbClient, './migrations', logger);

async function main() {
  const command = process.argv[2];
  const arg = process.argv[3];
  
  await dbClient.initialize();
  
  try {
    switch (command) {
      case 'up':
        await migrator.migrate();
        break;
      case 'down':
        if (arg) {
          await migrator.rollbackTo(arg);
        } else {
          await migrator.rollback();
        }
        break;
      case 'status':
        const status = await migrator.getStatus();
        console.log(`Applied migrations: ${status.length}`);
        status.forEach(m => console.log(`  ✓ ${m.id} - ${m.description}`));
        
        const pending = await migrator.getPending();
        if (pending.length > 0) {
          console.log(`\nPending migrations: ${pending.length}`);
          pending.forEach(m => console.log(`  ○ ${m.id} - ${m.description}`));
        }
        break;
      case 'reset':
        if (arg === '--confirm') {
          await migrator.reset();
        } else {
          console.log('Use: npm run migrate reset --confirm');
        }
        break;
      default:
        console.log('Usage: npm run migrate [up|down|status|reset] [migration_id|--confirm]');
    }
  } finally {
    await dbClient.close();
  }
}

main().catch(console.error);
// package.json scripts
{
  "scripts": {
    "migrate": "tsx scripts/migrate.ts",
    "migrate:up": "npm run migrate up",
    "migrate:down": "npm run migrate down",
    "migrate:status": "npm run migrate status",
    "migrate:reset": "npm run migrate reset"
  }
}

Integração com Express Adapter

// src/server.ts - Integração com @200systems/mf-express-adapter
import { ExpressApp } from '@200systems/mf-express-adapter';
import { dbClient } from './database.js';

const app = new ExpressApp({
  port: 3000,
  cors: { origin: true }
});

// Middleware de database (personalizado)
app.getApp().use((req, res, next) => {
  req.db = dbClient;
  next();
});

// Health check endpoint
app.addRoute({
  method: 'get',
  path: '/health',
  handler: async (req, res) => {
    const health = await checkDatabaseHealth();
    const statusCode = health.database.status === 'healthy' ? 200 : 503;
    res.status(statusCode).json(health);
  }
});

// Aplicar migrações na inicialização
const migrator = new MySQLMigrator(dbClient, './migrations', logger);
await dbClient.initialize();
await migrator.migrate();

await app.start();

Melhores Práticas

  1. Use o padrão Singleton para evitar múltiplas conexões desnecessárias
  2. Sempre inicialize o cliente antes de usar (await dbClient.initialize())
  3. Use transações para operações que modificam múltiplas tabelas
  4. Configure charset utf8mb4 para suporte completo a Unicode
  5. Use InnoDB engine para suporte a transações e foreign keys
  6. Implemente health checks para monitoramento de produção
  7. Configure SSL adequadamente em produção
  8. Use Repository pattern para organizar queries
  9. Monitore o pool de conexões em produção
  10. Teste migrações em ambiente de desenvolvimento primeiro
  11. Use índices apropriados para queries frequentes
  12. Configure timeouts adequados para sua aplicação

Dependencies

Required

  • @200systems/mf-logger - Sistema de logging
  • @200systems/mf-db-core - Abstrações de banco de dados

Peer Dependencies

  • mysql2 - Driver MySQL para Node.js (^3.6.0)

License

MIT