Add full-stack Hello World app with CI/CD
- Go backend with PostgreSQL connection - Next.js frontend with TypeScript - Docker Compose for local development - Woodpecker CI pipeline for build and push - Kubernetes manifests with Kustomize - ArgoCD application for GitOps deployment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
23
frontend/Dockerfile
Normal file
23
frontend/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
18
frontend/app/layout.tsx
Normal file
18
frontend/app/layout.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Hello World App',
|
||||
description: 'Go + PostgreSQL + Next.js',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
118
frontend/app/page.tsx
Normal file
118
frontend/app/page.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface ApiResponse {
|
||||
message: string
|
||||
db_status: string
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const [data, setData] = useState<ApiResponse | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080'
|
||||
|
||||
fetch(`${apiUrl}/api/hello`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setData(data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err.message)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<main style={styles.main}>
|
||||
<div style={styles.container}>
|
||||
<h1 style={styles.title}>Hello World!</h1>
|
||||
<p style={styles.subtitle}>Go + PostgreSQL + Next.js</p>
|
||||
|
||||
<div style={styles.card}>
|
||||
{loading && <p>Loading...</p>}
|
||||
{error && <p style={styles.error}>Error: {error}</p>}
|
||||
{data && (
|
||||
<>
|
||||
<p style={styles.message}>{data.message}</p>
|
||||
<p style={styles.status}>
|
||||
Database:
|
||||
<span style={{
|
||||
color: data.db_status === 'connected' ? '#22c55e' : '#ef4444',
|
||||
marginLeft: '8px'
|
||||
}}>
|
||||
{data.db_status}
|
||||
</span>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={styles.techStack}>
|
||||
<span style={styles.badge}>Go</span>
|
||||
<span style={styles.badge}>PostgreSQL</span>
|
||||
<span style={styles.badge}>Next.js</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
const styles: { [key: string]: React.CSSProperties } = {
|
||||
main: {
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
},
|
||||
container: {
|
||||
textAlign: 'center',
|
||||
color: 'white',
|
||||
},
|
||||
title: {
|
||||
fontSize: '4rem',
|
||||
marginBottom: '0.5rem',
|
||||
textShadow: '2px 2px 4px rgba(0,0,0,0.2)',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: '1.25rem',
|
||||
opacity: 0.9,
|
||||
marginBottom: '2rem',
|
||||
},
|
||||
card: {
|
||||
background: 'rgba(255,255,255,0.95)',
|
||||
borderRadius: '12px',
|
||||
padding: '2rem',
|
||||
color: '#333',
|
||||
boxShadow: '0 10px 40px rgba(0,0,0,0.2)',
|
||||
marginBottom: '2rem',
|
||||
},
|
||||
message: {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 500,
|
||||
},
|
||||
status: {
|
||||
marginTop: '1rem',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
error: {
|
||||
color: '#ef4444',
|
||||
},
|
||||
techStack: {
|
||||
display: 'flex',
|
||||
gap: '1rem',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
badge: {
|
||||
background: 'rgba(255,255,255,0.2)',
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '20px',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
}
|
||||
6
frontend/next.config.js
Normal file
6
frontend/next.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
22
frontend/package.json
Normal file
22
frontend/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "hello-world-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "14.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
26
frontend/tsconfig.json
Normal file
26
frontend/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user