| ORM | Manages | Approach |
|---|---|---|
| TypeORM | Users, Posts | Decorator-based entities (legacy system) |
| Drizzle | Comments, Tags | Schema-based definitions (modern system) |
npm installEdit .env file:
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USER=root
DATABASE_PASSWORD=your_password # ← SET THIS
DATABASE_NAME=blog_db
PORT=3001
NODE_ENV=developmentnpm run start:devOpen http://localhost:3001 in your browser
┌─────────────────────────────────────────────────────┐
│ NestJS Application │
│ │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ TypeORM │ │ Drizzle │ │
│ │ │ │ │ │
│ │ • Users │ │ • Comments │ │
│ │ • Posts │ │ • Tags │ │
│ │ │ │ • PostTags │ │
│ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ └─────────┬───────────────┘ │
│ │ │
└───────────────────┼───────────────────────────────┘
│
┌───────▼────────┐
│ MariaDB/MySQL │
│ blog_db │
│ │
│ Tables: │
│ • users │
│ • posts │
│ • comments │
│ • tags │
│ • post_tags │
└────────────────┘
blog-type-drizzle/
├── src/
│ ├── typeorm/
│ │ └── entities/
│ │ ├── user.entity.ts # TypeORM entity
│ │ └── post.entity.ts # TypeORM entity
│ ├── drizzle/
│ │ ├── schema.ts # Drizzle schema definitions
│ │ └── drizzle.provider.ts # Custom NestJS provider
│ ├── users/ # User module (TypeORM)
│ │ ├── users.controller.ts
│ │ ├── users.service.ts # Uses TypeORM + Drizzle
│ │ └── users.module.ts
│ ├── posts/ # Post module (TypeORM)
│ │ ├── posts.controller.ts
│ │ ├── posts.service.ts # Cross-ORM interactions
│ │ └── posts.module.ts
│ ├── comments/ # Comment module (Drizzle)
│ │ ├── comments.controller.ts
│ │ ├── comments.service.ts # Uses Drizzle + TypeORM
│ │ └── comments.module.ts
│ ├── tags/ # Tag module (Drizzle)
│ │ ├── tags.controller.ts
│ │ ├── tags.service.ts # Pure Drizzle
│ │ └── tags.module.ts
│ ├── dto/ # Data Transfer Objects
│ │ ├── create-user.dto.ts
│ │ ├── create-post.dto.ts
│ │ ├── create-comment.dto.ts
│ │ └── create-tag.dto.ts
│ ├── app.module.ts # Main module
│ ├── app.controller.ts # Health checks
│ ├── app.service.ts # Drizzle table initialization
│ └── main.ts # Application entry point
├── public/
│ └── index.html # Interactive dashboard
├── drizzle.config.ts # Drizzle Kit configuration
├── .env # Environment variables
├── package.json # Dependencies
├── tsconfig.json # TypeScript configuration
└── start-dev.sh # Startup script
- Node.js v16 or higher
- MariaDB/MySQL 5.7+ running on port 3306
- npm or yarn package manager
cd blog-type-drizzlenpm installExpected output:
added 812 packages in 38s
✅ Dependencies installed successfully
# Login to MySQL
mysql -u root -p
# Create database
CREATE DATABASE blog_db;
exit;Copy .env.example to .env and update:
cp .env.example .env
nano .env # or use your preferred editorSet your database password:
DATABASE_PASSWORD=your_actual_password_herenpm run buildnpm run start:devExpected output:
🔧 Initializing Drizzle ORM tables...
✅ Drizzle ORM tables initialized successfully
╔════════════════════════════════════════════════════════════╗
║ ║
║ 🚀 Hybrid ORM Blog API - TypeORM + Drizzle ║
║ ║
║ Server running on: http://localhost:3001 ║
║ Dashboard: http://localhost:3001 ║
║ ║
║ TypeORM manages: Users, Posts ║
║ Drizzle manages: Comments, Tags ║
║ ║
╚════════════════════════════════════════════════════════════╝
- Open http://localhost:3001 in your browser
- Use the interactive UI to:
- Create users, posts, comments, and tags
- Link posts to tags
- View all data with real-time updates
- Monitor system health
curl -X POST http://localhost:3001/users \
-H "Content-Type: application/json" \
-d '{
"email": "john@example.com",
"name": "John Doe",
"bio": "Software developer"
}'curl -X POST http://localhost:3001/posts \
-H "Content-Type: application/json" \
-d '{
"title": "Getting Started with Drizzle ORM",
"content": "Drizzle is a modern TypeScript ORM...",
"authorId": 1
}'curl -X POST http://localhost:3001/tags \
-H "Content-Type: application/json" \
-d '{
"name": "typescript",
"description": "TypeScript related posts"
}'curl -X POST http://localhost:3001/posts/1/tags/1curl -X POST http://localhost:3001/comments \
-H "Content-Type: application/json" \
-d '{
"content": "Great article!",
"userId": 1,
"postId": 1
}'All API endpoints are REST-compliant and can be easily imported into any API client.
| Method | Endpoint | Description |
|---|---|---|
| POST | /users |
Create a new user |
| GET | /users |
Get all users (with comment counts) |
| GET | /users/:id |
Get user by ID |
| PATCH | /users/:id |
Update user |
| DELETE | /users/:id |
Delete user |
| Method | Endpoint | Description |
|---|---|---|
| POST | /posts |
Create a new post |
| GET | /posts |
Get all posts (with tags & comments) |
| GET | /posts/:id |
Get post by ID |
| GET | /posts/:id/details |
Get post with full details |
| GET | /posts/author/:authorId |
Get posts by author |
| POST | /posts/:postId/tags/:tagId |
Link post to tag (Cross-ORM) |
| GET | /posts/:id/tags |
Get all tags for a post |
| PATCH | /posts/:id |
Update post |
| DELETE | /posts/:id |
Delete post |
| Method | Endpoint | Description |
|---|---|---|
| POST | /comments |
Create a new comment |
| GET | /comments |
Get all comments (with author info) |
| GET | /comments/:id |
Get comment by ID |
| GET | /comments/post/:postId |
Get comments by post |
| GET | /comments/user/:userId |
Get comments by user |
| PATCH | /comments/:id |
Update comment |
| DELETE | /comments/:id |
Delete comment |
| Method | Endpoint | Description |
|---|---|---|
| POST | /tags |
Create a new tag |
| GET | /tags |
Get all tags (with post counts) |
| GET | /tags/:id |
Get tag by ID |
| GET | /tags/name/:name |
Get tag by name |
| GET | /tags/:id/posts |
Get posts for a tag |
| PATCH | /tags/:id |
Update tag |
| DELETE | /tags/:id |
Delete tag |
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
API information |
| GET | /health |
System health check |
File: src/posts/posts.service.ts
async enrichPostWithDrizzleData(post: Post): Promise<void> {
// Get comment count from Drizzle
const [commentResult] = await this.drizzleDb
.select({ count: count() })
.from(schema.comments)
.where(eq(schema.comments.postId, post.id));
post.commentCount = commentResult?.count || 0;
// Get tags from Drizzle
const tagsData = await this.drizzleDb
.select({
id: schema.tags.id,
name: schema.tags.name,
})
.from(schema.tags)
.innerJoin(schema.postTags, eq(schema.postTags.tagId, schema.tags.id))
.where(eq(schema.postTags.postId, post.id));
post.tags = tagsData;
post.tagCount = tagsData.length;
}File: src/comments/comments.service.ts
async create(createCommentDto: CreateCommentDto): Promise<schema.Comment> {
// Verify user exists in TypeORM
const user = await this.typeormDataSource
.getRepository('User')
.findOne({ where: { id: createCommentDto.userId } });
if (!user) {
throw new NotFoundException(`User with ID ${createCommentDto.userId} not found`);
}
// Verify post exists in TypeORM
const post = await this.typeormDataSource
.getRepository('Post')
.findOne({ where: { id: createCommentDto.postId } });
if (!post) {
throw new NotFoundException(`Post with ID ${createCommentDto.postId} not found`);
}
// Create comment using Drizzle
const result = await this.drizzleDb.insert(schema.comments).values({
content: createCommentDto.content,
userId: createCommentDto.userId,
postId: createCommentDto.postId,
});
return comment;
}File: src/posts/posts.service.ts
async linkToTag(postId: number, tagId: number): Promise<{ message: string }> {
// Verify post exists (TypeORM)
await this.findOne(postId);
// Verify tag exists (Drizzle)
const [tag] = await this.drizzleDb
.select()
.from(schema.tags)
.where(eq(schema.tags.id, tagId))
.limit(1);
if (!tag) {
throw new NotFoundException(`Tag with ID ${tagId} not found`);
}
// Link them (Drizzle)
try {
await this.drizzleDb.insert(schema.postTags).values({
postId,
tagId,
});
} catch (error) {
if (error.code !== 'ER_DUP_ENTRY') {
throw error;
}
}
return { message: `Post ${postId} linked to tag ${tagId}` };
}CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
bio TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
published BOOLEAN DEFAULT TRUE,
author_id INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE
);CREATE TABLE comments (
id INT AUTO_INCREMENT PRIMARY KEY,
content TEXT NOT NULL,
user_id INT NOT NULL,
post_id INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE
);CREATE TABLE tags (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);CREATE TABLE post_tags (
post_id INT NOT NULL,
tag_id INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);The startup script automatically handles this, but if needed:
lsof -ti:3001 | xargs kill -9
npm run start:dev# Check MySQL is running
mysql -u root -p
# Verify database exists
mysql -u root -p -e "SHOW DATABASES LIKE 'blog_db';"
# Create if needed
mysql -u root -p -e "CREATE DATABASE blog_db;"
# Check credentials in .env
cat .env | grep DATABASE_Check console output for:
🔧 Initializing Drizzle ORM tables...
✅ Drizzle ORM tables initialized successfully
If you see warnings, tables might already exist (this is fine).
Verify manually:
mysql -u root -p blog_db -e "SHOW TABLES;"Should see: users, posts, comments, tags, post_tags
rm -rf node_modules package-lock.json
npm install# Clean build
rm -rf dist
npm run buildCheck logs for specific errors:
npm run start:dev 2>&1 | tee debug.log-
Start development server:
npm run start:dev
-
Make changes to TypeScript files (auto-reloads)
-
Test via dashboard: http://localhost:3001
-
Generate Drizzle migrations (when schema changes):
npm run drizzle:generate
-
Run migrations:
npm run drizzle:migrate
-
Inspect database visually:
npm run drizzle:studio
- Formatter: Prettier
- Linter: ESLint
- Type Checker: TypeScript strict mode
Run code quality checks:
npm run format
npm run lint# Unit tests
npm test
# E2E tests
npm run test:e2e
# Test coverage
npm run test:cov| Script | Description |
|---|---|
npm run start:dev |
Start development server with hot reload |
npm run build |
Build for production |
npm run start:prod |
Run production build |
npm run format |
Format code with Prettier |
npm run lint |
Lint code with ESLint |
npm run test |
Run unit tests |
npm run test:e2e |
Run end-to-end tests |
npm run test:cov |
Generate test coverage |
npm run drizzle:generate |
Generate Drizzle migrations |
npm run drizzle:migrate |
Run Drizzle migrations |
npm run drizzle:studio |
Open Drizzle Studio (visual DB tool) |
After installation, verify:
-
npm installcompleted without errors -
.envconfigured with database credentials -
npm run buildsuccessful - Server starts on port 3001
- Dashboard loads at http://localhost:3001
- All 5 tables created in database
- Can create users successfully
- Can create posts successfully
- Can create tags successfully
- Can link posts to tags
- Can create comments successfully
- Posts show tag counts (cross-ORM works)
- Comments show author info (cross-ORM works)
- No errors in console logs