node-springboot
v1.2.0
Published
A Node.js framework that mimics Spring Boot's structure using TypeScript decorators. Features JPA-like ORM, dependency injection, REST controllers, relationship mappings, rate limiting, and security features.
Maintainers
Readme
node-springboot
A powerful Node.js framework that mimics Spring Boot's structure using TypeScript decorators. Build REST APIs with JPA-like ORM, dependency injection, relationship mappings, rate limiting, and comprehensive security features.
Features
- 🎯 JPA-like ORM: Entity management with automatic table creation and migrations
- 🔗 Relationship Mappings: OneToOne, OneToMany, ManyToOne, ManyToMany with cascade operations
- 💉 Dependency Injection: Spring-like
@Service,@Repository,@Controller,@Autowired - 🚀 REST Controllers: Automatic route registration with decorators
- 🔒 Security: SQL injection prevention, XSS protection, rate limiting, security headers
- ⚡ Performance: Optimized batch loading with JOINs (no N+1 queries)
- 📊 Pagination & Sorting: Built-in support for paginated queries
- 🔄 Serialization: Spring Boot-style
@JsonManagedReferenceand@JsonBackReference - 📝 TypeScript: Full type safety with decorators and metadata
- 🔍 Spring Data JPA Features: Method name-based query derivation,
@Query,@Modifying,@Param, batch operations
Installation
npm install node-springbootQuick Start
1. Create an Entity
import { Entity, Table, Column, Id, GeneratedValue } from 'node-springboot';
@Entity()
@Table('users')
export class User {
@Id
@GeneratedValue({ strategy: 'AUTO' })
@Column({ name: 'id', type: 'INT' })
id?: number;
@Column({ name: 'username', type: 'VARCHAR(100)', nullable: false })
username!: string;
@Column({ name: 'email', type: 'VARCHAR(255)', nullable: false })
email!: string;
}2. Create a Repository
import { Repository, JpaRepository, EntityManager, Query, Param } from 'node-springboot';
import { User } from './entities/User';
@Repository
export class UserRepository extends JpaRepository<User, number> {
constructor(entityManager: EntityManager) {
super(entityManager, User);
}
// Method name-based queries (automatic - no implementation needed!)
async findByEmail(email: string): Promise<User | null>;
async findByUsernameAndPassword(username: string, password: string): Promise<User | null>;
async findByAgeGreaterThan(age: number): Promise<User[]>;
async countByStatus(status: string): Promise<number>;
async existsByEmail(email: string): Promise<boolean>;
async deleteByStatus(status: string): Promise<number>;
// Custom queries with @Query
@Query("SELECT * FROM users WHERE email = :email AND status = :status", { nativeQuery: true })
async findByEmailAndStatus(
@Param('email') email: string,
@Param('status') status: string
): Promise<User[]>;
}3. Create a Service
import { Service, Autowired } from 'node-springboot';
import { UserRepository } from './repositories/UserRepository';
import { User } from './entities/User';
@Service
export class UserService {
@Autowired
private userRepository!: UserRepository;
async findAll(): Promise<User[]> {
return this.userRepository.findAll();
}
async findById(id: number): Promise<User | null> {
return this.userRepository.findById(id);
}
async create(user: User): Promise<User> {
return this.userRepository.save(user);
}
}4. Create a REST Controller
import {
RestController,
Get,
Post,
Put,
Delete,
RequestBody,
PathVariable,
Autowired
} from 'node-springboot';
import { UserService } from './services/UserService';
import { User } from './entities/User';
@RestController({ path: '/api/users' })
export class UserController {
@Autowired
private userService!: UserService;
@Get()
async getAllUsers(): Promise<User[]> {
return this.userService.findAll();
}
@Get('/:id')
async getUserById(@PathVariable('id') id: number): Promise<User | null> {
return this.userService.findById(id);
}
@Post()
async createUser(@RequestBody() user: User): Promise<User> {
return this.userService.create(user);
}
@Put('/:id')
async updateUser(
@PathVariable('id') id: number,
@RequestBody() user: Partial<User>
): Promise<User | null> {
return this.userService.update(id, user);
}
@Delete('/:id')
async deleteUser(@PathVariable('id') id: number): Promise<{ success: boolean }> {
const result = await this.userService.delete(id);
return { success: result };
}
}5. Bootstrap Your Application
import 'reflect-metadata';
import { Application } from 'node-springboot';
import { User } from './entities/User';
import { UserRepository } from './repositories/UserRepository';
import { UserService } from './services/UserService';
import { UserController } from './controllers/UserController';
async function bootstrap() {
const app = new Application({
port: 3000,
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '3306'),
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'myapp_db'
},
rateLimit: {
windowMs: 60000, // 1 minute
maxRequests: 100 // 100 requests per minute
}
});
// Initialize application
await app.initialize();
// Register entities
await app.registerEntities([User]);
// Register components
app.registerComponents([
UserRepository,
UserService,
UserController
]);
// Register routes
app.registerRoutes();
// Start server
await app.start();
}
bootstrap();Relationship Mappings
ManyToOne
@Entity()
@Table('orders')
export class Order {
@Id
@GeneratedValue({ strategy: 'AUTO' })
@Column({ name: 'id', type: 'INT' })
id?: number;
@ManyToOne({ targetEntity: () => User, fetch: FetchType.EAGER })
@JoinColumn({ name: 'user_id' })
user!: User;
}OneToMany
@Entity()
@Table('orders')
export class Order {
@OneToMany({
targetEntity: () => OrderItem,
mappedBy: 'order',
cascade: [CascadeType.ALL],
fetch: FetchType.EAGER
})
@JsonManagedReference()
items!: OrderItem[];
}ManyToMany
@Entity()
@Table('students')
export class Student {
@ManyToMany({
targetEntity: () => Course,
fetch: FetchType.EAGER,
cascade: [CascadeType.ALL]
})
@JoinTable({
name: 'student_courses',
joinColumn: { name: 'student_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'course_id', referencedColumnName: 'id' }
})
@JsonManagedReference()
courses!: Course[];
}Pagination & Sorting
// In your controller
@Get()
async getUsers(
@RequestParam('page') page?: string,
@RequestParam('size') size?: string,
@RequestParam('sort') sort?: string,
@RequestParam('direction') direction?: string
): Promise<User[] | Page<User>> {
if (page !== undefined || size !== undefined) {
const pageable = createPageable(page, size, sort, direction);
return this.userService.findAll(pageable);
}
return this.userService.findAll();
}Rate Limiting
Rate limiting is enabled by default (100 requests per minute). Configure via:
const app = new Application({
// ...
rateLimit: {
windowMs: 60000, // Time window in milliseconds
maxRequests: 100, // Max requests per window
message: 'Too many requests, please try again later.'
}
});Or via environment variables:
RATE_LIMIT_ENABLED=true
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100Security Features
- ✅ SQL Injection Prevention (parameterized queries)
- ✅ XSS Protection (input sanitization)
- ✅ DoS Protection (request size limits, rate limiting)
- ✅ Security Headers (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection)
- ✅ Path Traversal Prevention
- ✅ Input Validation & Sanitization
API Documentation
Decorators
Entity Decorators:
@Entity()- Mark class as entity@Table(name)- Specify table name@Column(options)- Define column@Id- Mark as primary key@GeneratedValue(options)- Auto-increment strategy
Spring Decorators:
@Service- Mark as service@Repository- Mark as repository@Controller- Mark as controller@RestController({ path })- REST controller with base path@Autowired- Dependency injection@Get(path),@Post(path),@Put(path),@Delete(path)- HTTP methods@RequestBody,@PathVariable(name),@RequestParam(name)- Parameter injection
Relationship Decorators:
@OneToOne,@OneToMany,@ManyToOne,@ManyToMany- Relationship types@JoinColumn(options)- Foreign key column@JoinTable(options)- Join table for ManyToMany
Serialization Decorators:
@JsonManagedReference()- Forward reference (serialized)@JsonBackReference()- Back reference (excluded)@JsonIgnore()- Exclude from serialization
Query Decorators:
@Query(value, options?)- Custom SQL/JPQL query@Modifying- Mark query as modifying (update/delete)@Param(name)- Named parameter in queries
Spring Data JPA Features
This framework now supports Spring Data JPA-style method name-based query derivation! No need to write SQL for common queries.
Method Name-Based Queries
Just declare the method - the framework generates the SQL automatically:
@Repository
export class UserRepository extends JpaRepository<User, number> {
// No implementation needed!
async findByEmail(email: string): Promise<User | null>;
async findByUsernameAndPassword(username: string, password: string): Promise<User | null>;
async findByAgeGreaterThan(age: number): Promise<User[]>;
async findByStatusIn(statuses: string[]): Promise<User[]>;
async findFirst10ByOrderByCreatedAtDesc(): Promise<User[]>;
async countByStatus(status: string): Promise<number>;
async existsByEmail(email: string): Promise<boolean>;
async deleteByStatus(status: string): Promise<number>;
}Supported Query Keywords
- Logical:
And,Or - Comparison:
GreaterThan,LessThan,GreaterThanEqual,LessThanEqual,Equals,NotEquals - String:
Like,Containing,StartingWith,EndingWith,IgnoreCase - Collection:
In,NotIn - Null:
IsNull,IsNotNull - Range:
Between - Modifiers:
First/Top,Distinct,OrderBy,Asc/Desc
Custom Queries
Use @Query for complex queries:
@Query("SELECT * FROM users WHERE email = :email", { nativeQuery: true })
async findByEmail(@Param('email') email: string): Promise<User[]>;
@Modifying
@Query("UPDATE users SET status = ?1 WHERE id = ?2", { nativeQuery: true })
async updateStatus(status: string, id: number): Promise<number>;Batch Operations
await userRepository.saveAll([user1, user2, user3]);
await userRepository.findAllById([1, 2, 3]);
await userRepository.deleteAll();
await userRepository.deleteAllById([1, 2, 3]);See SPRING_DATA_JPA_FEATURES.md for complete documentation.
Examples
See the examples directory for complete working examples.
Requirements
- Node.js >= 14.0.0
- TypeScript >= 4.0.0
- MySQL 5.7+ or MariaDB
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues, questions, or contributions, please open an issue on GitHub.
