test first commit
This commit is contained in:
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Multi-stage build for backend service
|
||||||
|
# Stage 1: Builder
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Stage 2: Production
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
|
adduser -S nodejs -u 1001
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy dependencies from builder
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY --chown=nodejs:nodejs src ./src
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER nodejs
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||||
|
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
|
||||||
|
|
||||||
|
# Start application
|
||||||
|
CMD ["node", "src/server.js"]
|
||||||
158
README.md
Normal file
158
README.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# Inventory Backend Service
|
||||||
|
|
||||||
|
Minimal Node.js/Express backend for inventory management. Focus: **DevOps/CI-CD demo**, not application complexity.
|
||||||
|
|
||||||
|
## Features (Minimal by Design)
|
||||||
|
- 4 API endpoints: GET/POST items, health check, readiness check
|
||||||
|
- MySQL database with single table (4 fields)
|
||||||
|
- Input validation
|
||||||
|
- CORS and security headers
|
||||||
|
- Non-root Docker container
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
- Node.js 20
|
||||||
|
- Express 4
|
||||||
|
- MySQL 8.0
|
||||||
|
- mysql2 (prepared statements for SQL injection protection)
|
||||||
|
- helmet (security headers)
|
||||||
|
- express-validator
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js 20+
|
||||||
|
- MySQL 8.0 running locally or via Docker
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
```bash
|
||||||
|
# 1. Start MySQL
|
||||||
|
docker run -d \
|
||||||
|
--name mysql-local \
|
||||||
|
-e MYSQL_ROOT_PASSWORD=rootpass \
|
||||||
|
-e MYSQL_DATABASE=inventory \
|
||||||
|
-p 3306:3306 \
|
||||||
|
mysql:8.0
|
||||||
|
|
||||||
|
# 2. Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 3. Create .env file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 4. Start server
|
||||||
|
npm start
|
||||||
|
# Or with hot-reload:
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Run linter
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
#### Health Check (Liveness)
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
# Response: {"status": "ok"}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Readiness Check
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/ready
|
||||||
|
# Response: {"status": "ready", "database": "connected"}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Get All Items
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/items
|
||||||
|
# Response: [{"id":1,"name":"Laptop","quantity":10,"price":"999.99"},...]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Create Item
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/api/items \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"Monitor","quantity":15,"price":299.99}'
|
||||||
|
# Response: {"id":4,"name":"Monitor","quantity":15,"price":299.99,"message":"Item created successfully"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build image
|
||||||
|
docker build -t backend-service:latest .
|
||||||
|
|
||||||
|
# Run container
|
||||||
|
docker run -d \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-e DB_HOST=host.docker.internal \
|
||||||
|
-e DB_PASSWORD=rootpass \
|
||||||
|
backend-service:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD Pipeline
|
||||||
|
|
||||||
|
Gitea Actions workflow:
|
||||||
|
1. Checkout → Install → Lint → Test
|
||||||
|
2. SonarQube scan + quality gate
|
||||||
|
3. Build multi-stage Docker image
|
||||||
|
4. Push to Gitea registry with tags: `{branch}-{sha}`, `{branch}`, `latest`
|
||||||
|
5. Update k8s-manifests repo
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE items (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
quantity INT NOT NULL DEFAULT 0,
|
||||||
|
price DECIMAL(10, 2) NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**That's it!** No timestamps, no foreign keys, no complexity. Focus on CI/CD.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| PORT | 3000 | Server port |
|
||||||
|
| DB_HOST | localhost | MySQL host |
|
||||||
|
| DB_PORT | 3306 | MySQL port |
|
||||||
|
| DB_USER | root | MySQL user |
|
||||||
|
| DB_PASSWORD | rootpass | MySQL password |
|
||||||
|
| DB_NAME | inventory | Database name |
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- ✓ Non-root Docker user (nodejs:1001)
|
||||||
|
- ✓ Helmet.js security headers
|
||||||
|
- ✓ SQL injection protection (prepared statements)
|
||||||
|
- ✓ Input validation (express-validator)
|
||||||
|
- ✓ CORS configured
|
||||||
|
- ✓ Multi-stage Docker build (no dev dependencies in prod)
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
backend-service/
|
||||||
|
├── src/
|
||||||
|
│ ├── server.js # Main Express app (~100 lines, all routes inline)
|
||||||
|
│ ├── db.js # MySQL connection pool
|
||||||
|
│ └── init.sql # Database schema
|
||||||
|
├── tests/
|
||||||
|
│ └── server.test.js # Basic API tests
|
||||||
|
├── .gitea/workflows/
|
||||||
|
│ └── ci.yaml # CI/CD pipeline
|
||||||
|
├── Dockerfile # Multi-stage build
|
||||||
|
├── package.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total: ~10 files** - intentionally minimal!
|
||||||
17
jest.config.js
Normal file
17
jest.config.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'node',
|
||||||
|
coverageDirectory: 'coverage',
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'src/**/*.js',
|
||||||
|
'!src/init.sql'
|
||||||
|
],
|
||||||
|
testMatch: ['**/tests/**/*.test.js'],
|
||||||
|
coverageThreshold: {
|
||||||
|
global: {
|
||||||
|
branches: 50,
|
||||||
|
functions: 50,
|
||||||
|
lines: 50,
|
||||||
|
statements: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
5538
package-lock.json
generated
Normal file
5538
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "backend-service",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Minimal inventory management backend for DevOps demo",
|
||||||
|
"main": "src/server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/server.js",
|
||||||
|
"dev": "nodemon src/server.js",
|
||||||
|
"test": "jest --coverage",
|
||||||
|
"lint": "eslint src/"
|
||||||
|
},
|
||||||
|
"keywords": ["inventory", "express", "mysql"],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"mysql2": "^3.6.5",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"helmet": "^7.1.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"express-validator": "^7.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.2",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"supertest": "^6.3.3",
|
||||||
|
"eslint": "^8.55.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user