This guide provides practical, step-by-step instructions for adding new endpoints, entities, services, and background workers in AudioBlock_Backend, along with common debugging tips and development pitfalls.
To add a new feature endpoint (e.g. POST /api/songs/:id/like), follow these 5 steps:
import { IsUUID, IsNotEmpty } from 'class-validator';
export class LikeSongDto {
@IsUUID()
@IsNotEmpty()
songId!: string;
}async likeSong(userId: string, songId: string): Promise<void> {
const song = await this.songRepository.findOneBy({ id: songId });
if (!song) {
throw AppError.notFound('Song not found');
}
// Execute business logic (e.g., record like entry)
}likeSong = async (req: Request, res: Response): Promise<void> => {
try {
const userId = req.user.id;
const { songId } = req.body;
await this.songService.likeSong(userId, songId);
res.status(200).json({ success: true, message: 'Song liked successfully' });
} catch (error) {
handleError(req, res, error);
}
};import { validateDTO } from '../middlewares/validate';
import { LikeSongDto } from '../dtos/LikeSongDto';
router.post(
'/:id/like',
authMiddleware,
validateDTO(LikeSongDto),
songController.likeSong,
);app.use('/api/songs', songRouter);import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './User';
@Entity('playlists')
export class Playlist {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column()
name!: string;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user!: User;
@Column()
userId!: string;
@CreateDateColumn()
createdAt!: Date;
}Add Playlist to the entities array of AppDataSource.
# Generate migration automatically based on entity diff
npm run migration:generate -- src/migrations/AddPlaylistTable
# Run pending migrations
npm run migration:runimport { AppDataSource } from '../config/db';
import { Playlist } from '../entities/Playlist';
export class PlaylistService {
private playlistRepo = AppDataSource.getRepository(Playlist);
async createPlaylist(userId: string, name: string): Promise<Playlist> {
const playlist = this.playlistRepo.create({ userId, name });
return await this.playlistRepo.save(playlist);
}
}import { PlaylistService } from './services/PlaylistService';
// Register instance in container
container.register('PlaylistService', new PlaylistService());export async function processEmailJob(data: { email: string; subject: string; body: string }): Promise<void> {
// Send email logic via Nodemailer/SendGrid
}import { queueManager } from '../workers/QueueManager';
await queueManager.addJob('send_email', {
email: user.email,
subject: 'Welcome to AudioBlock',
body: 'Thank you for joining!',
});Use the pre-configured .vscode/launch.json configuration to attach the Node debugger:
- Open the Debug tab in VS Code.
- Select "Debug Backend (ts-node-dev)".
- Set breakpoints inside controllers or services.
# Run full test suite with Jest
npm test
# Run tests in watch mode
npm run test:watch
# Run a specific test file
npx jest src/middlewares/__tests__/validate.test.ts- Circular Dependencies: Do not directly instantiate
ServiceAinsideServiceBconstructor. Usecontainer.tsorServiceRegistry.ts. - Missing
awaiton Database Operations: Forgettingawaiton TypeORM calls will swallow errors or leak unhandled promises. - Exposing Sensitive Fields in Responses: Always omit
passwordHash,twoFactorSecret, or tokens before returningUserentities.