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