Modern applications rely heavily on APIs to interact with databases efficiently. Well-designed database APIs improve performance, maintainability, and security.
The Repository Pattern abstracts database operations behind a clean interface, decoupling business logic from data access.
interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
delete(id: string): Promise<void>;
}
class PostgresUserRepository implements UserRepository {
constructor(private pool: Pool) {}
async findById(id: string): Promise<User | null> {
const result = await this.pool.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result.rows[0] || null;
}
async save(user: User): Promise<void> {
await this.pool.query(
'INSERT INTO users(id, name, email) VALUES($1, $2, $3) ON CONFLICT(id) DO UPDATE SET name = $2, email = $3',
[user.id, user.name, user.email]
);
}
}
The Data Mapper Pattern separates domain objects from database schema, allowing independent evolution.
class UserMapper:
@staticmethod
def to_domain(row: dict) -> User:
return User(
id=row['id'],
name=row['name'],
email=row['email']
)
@staticmethod
def to_persistence(user: User) -> dict:
return {
'id': user.id,
'name': user.name,
'email': user.email
}
# Usage
user = UserMapper.to_domain(db_row)
db_data = UserMapper.to_persistence(user)
CQRS separates read and write operations, optimizing for performance and scalability.
// Command (Write) Side
public class UserCommandService {
private readonly IUserRepository _repository;
public async Task CreateUser(UserCreateDto dto) {
var user = new User(dto.Name, dto.Email);
await _repository.Save(user);
}
}
// Query (Read) Side
public class UserQueryService {
private readonly IUserReadRepository _readRepository;
public async Task<UserDto> GetUser(Guid id) {
return await _readRepository.GetById(id);
}
}
The Active Record Pattern combines data access and domain logic in a single object.
class User < ApplicationRecord
validates :email, presence: true, uniqueness: true
def activate
update(active: true)
end
end
# Usage
user = User.find(1)
user.activate
Connection pooling manages database connections efficiently, reducing overhead.
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost/db");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(10);
HikariDataSource dataSource = new HikariDataSource(config);
// Usage
try (Connection conn = dataSource.getConnection()) {
// Execute queries
}
Efficiently handle large datasets with pagination and filtering.
type Query {
users(
first: Int
after: String
filter: UserFilter
): UserConnection!
}
input UserFilter {
name: String
email: String
}
GET /api/users?limit=20&offset=40&name=John
Reduce round-trips with batch inserts/updates.
-- Batch Insert
INSERT INTO users (name, email) VALUES
('John', 'john@example.com'),
('Jane', 'jane@example.com');
-- Batch Update (PostgreSQL)
UPDATE users SET
name = tmp.name,
email = tmp.email
FROM (VALUES
(1, 'John Updated', 'john@example.com'),
(2, 'Jane Updated', 'jane@example.com')
) AS tmp(id, name, email)
WHERE users.id = tmp.id;