A NestJS-based Task API using pure SQL queries with PostgreSQL instead of TypeORM.
This project has been successfully migrated from TypeORM to pure SQL. All database operations now use direct PostgreSQL queries with parameterized statements for security.
src/
├── auth/ # Authentication module (unchanged)
│ ├── auth.module.ts
│ ├── auth.service.ts
│ ├── auth.resolver.ts
│ ├── dto/
│ │ └── auth.dto.ts
│ ├── guards/
│ │ └── jwt.guard.ts
│ └── strategies/
│ └── jwt.strategy.ts
├── database/ # NEW: Pure SQL database service
│ ├── database.module.ts
│ └── database.service.ts
├── users/ # Users module (updated for pure SQL)
│ ├── users.module.ts
│ ├── users.service.ts
│ ├── users.resolver.ts
│ └── entities/
│ └── user.entity.ts (simplified)
├── config/
│ └── database.config.ts # Updated for pg pool config
├── app.module.ts # Updated (removed TypeORM)
├── app.controller.ts
├── app.service.ts
└── main.ts
The new DatabaseService (src/database/database.service.ts) provides:
- Connection pooling: Manages multiple connections efficiently
- Query execution:
query(text, params)method for running SQL queries - Transactions:
transaction(callback)method for ACID transactions - Auto-initialization: Creates tables on module initialization
- Clean lifecycle: Properly closes connections on module destruction
// Execute a query
const result = await databaseService.query(
'SELECT * FROM users WHERE email = $1',
['user@example.com']
);
// Get raw client for complex operations
const client = await databaseService.getClient();
// Execute transaction
await databaseService.transaction(async (client) => {
await client.query('INSERT INTO users ...');
await client.query('INSERT INTO tasks ...');
});All database operations use parameterized SQL queries to prevent SQL injection:
Create User:
INSERT INTO users (email, password, "firstName", "lastName")
VALUES ($1, $2, $3, $4)
RETURNING id, email, password, "firstName", "lastName", "isVerified", "createdAt", "updatedAt"Find by Email:
SELECT id, email, password, "firstName", "lastName", "isVerified", "createdAt", "updatedAt"
FROM users
WHERE email = $1Find by ID:
SELECT id, email, password, "firstName", "lastName", "isVerified", "createdAt", "updatedAt"
FROM users
WHERE id = $1See SQL_QUERIES.md for detailed query documentation.
- Node.js 18+
- PostgreSQL 12+
- npm or yarn
- Clone and install:
npm install- Create
.envfile:
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_USER=postgres
DATABASE_PASSWORD=admin
DATABASE_NAME=task_api_db
JWT_SECRET=your-secret-key
NODE_ENV=development- Initialize database (creates schema):
npm run db:init- Start development server:
npm run start:devThe GraphQL API will be available at http://localhost:3000/graphql
# Build the application
npm run build
# Start development server with watch mode
npm run start:dev
# Start debug mode
npm run start:debug
# Production build
npm run build && npm run start:prod
# Initialize database schema
npm run db:init
# Format code
npm run format
# Lint code
npm run lint
# Run tests
npm run test
# Run tests with coverage
npm run test:cov
# Run e2e tests
npm run test:e2e- JWT-based authentication
- Password hashing with bcryptjs
- Passport.js strategy integration
- Protected GraphQL mutations and queries
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
"firstName" VARCHAR(255) NOT NULL,
"lastName" VARCHAR(255) NOT NULL,
"isVerified" BOOLEAN DEFAULT false,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);Sign Up:
mutation {
signUp(input: {
email: "user@example.com"
password: "password123"
firstName: "John"
lastName: "Doe"
}) {
accessToken
id
email
firstName
lastName
}
}Sign In:
mutation {
signIn(input: {
email: "user@example.com"
password: "password123"
}) {
accessToken
id
email
firstName
lastName
}
}Get Current User (requires authentication):
query {
getUser {
id
email
firstName
lastName
isVerified
}
}Get User by ID:
query {
getUserById(id: "uuid-here") {
id
email
firstName
lastName
}
}- ✅ SQL injection prevention (parameterized queries)
- ✅ Password hashing (bcryptjs)
- ✅ JWT authentication
- ✅ Type-safe database operations
- ✅ CORS protected endpoints
- ✅ Class validation with class-validator
See MIGRATION.md for detailed information about the migration from TypeORM to pure SQL.
- Removed
@nestjs/typeormandtypeormdependencies - Created
DatabaseServicefor connection pooling and query execution - Replaced repository pattern with direct SQL queries
- Simplified entity definitions (GraphQL types only)
- Updated all service methods to use
DatabaseService
The application includes:
- Unit tests for services
- E2E tests for API endpoints
- Jest configuration for test running
# Run unit tests
npm run test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage report
npm run test:cov
# Run E2E tests
npm run test:e2eAll configuration is managed through environment variables:
| Variable | Default | Description |
|---|---|---|
DATABASE_HOST |
127.0.0.1 | PostgreSQL host |
DATABASE_PORT |
5432 | PostgreSQL port |
DATABASE_USER |
postgres | Database user |
DATABASE_PASSWORD |
admin | Database password |
DATABASE_NAME |
task_api_db | Database name |
JWT_SECRET |
secret | JWT signing secret |
NODE_ENV |
development | Environment mode |
MIGRATION.md- Detailed migration guideSQL_QUERIES.md- Reference for all SQL queries usedSETUP.md- Setup instructionsPSQL_HINTS.md- PostgreSQL tips and hints
When adding new features:
- Write SQL queries with parameterized statements
- Add methods to
DatabaseServiceif needed - Use the query method from services
- Update documentation with new queries
- Ensure type safety with TypeScript interfaces
UNLICENSED
For issues or questions about the pure SQL implementation, refer to:
- SQL_QUERIES.md for query examples
- MIGRATION.md for migration details
- DatabaseService comments for API usage