Nx Monorepo Seri 2: Pengembangan Full-Stack dengan Spring Boot & React
Bagian 2 dari 5: Membangun Aplikasi Full-Stack Lengkap dalam Nx Monorepo
Pelajari cara membangun aplikasi full-stack lengkap dengan beberapa microservices Spring Boot, shared utility libraries, dan frontend React - semuanya dikelola dalam satu Nx monorepo.
Apa yang Akan Kita Bangun
Dalam tutorial ini, kita akan membuat aplikasi full-stack lengkap dengan:
Arsitektur Backend
- Service A: Service Manajemen User (Spring Boot)
- Service B: Service Manajemen Order (Spring Boot)
- Utility Library: Kode bersama antar services (Java)
Frontend
- Aplikasi React: Dashboard yang mengkonsumsi kedua backend services
- TypeScript: Integrasi API yang type-safe
- Component-based UI: Komponen UserList dan OrderList
Gambaran Arsitektur
demo-monorepository-2/
โโโ service-a/ # User Management API (Port 8081)
โ โโโ src/
โ โ โโโ main/java/com/kreasipositif/servicea/
โ โ โโโ controller/ # REST Controllers
โ โ โโโ model/ # Domain Models
โ โ โโโ service/ # Business Logic
โ โ โโโ config/ # Konfigurasi CORS
โ โโโ pom.xml
โโโ service-b/ # Order Management API (Port 8082)
โ โโโ src/
โ โ โโโ main/java/com/kreasipositif/serviceb/
โ โ โโโ controller/ # REST Controllers
โ โ โโโ model/ # Domain Models
โ โ โโโ service/ # Business Logic
โ โ โโโ config/ # Konfigurasi CORS
โ โโโ pom.xml
โโโ utility-library/ # Shared Java Utilities
โ โโโ src/
โ โ โโโ main/java/com/kreasipositif/utility/
โ โโโ pom.xml
โโโ frontend/ # React Dashboard (Port 3000)
โ โโโ src/
โ โ โโโ components/ # React Components
โ โ โโโ services/ # API Service Layer
โ โ โโโ App.tsx
โ โโโ package.json
โโโ nx.json # Konfigurasi Nx
โโโ pom.xml # Konfigurasi Maven Root
Prasyarat
Sebelum memulai, pastikan Anda memiliki:
# Node.js v18 atau lebih tinggi
node --version
# Java 17 atau lebih tinggi
java -version
# Maven 3.6+
mvn --version
# Git
git --version
Seri 1 Sudah Selesai: Anda harus sudah memiliki setup Nx monorepo dasar. Jika belum, lihat Seri 1: Pengenalan & Integrasi Spring Boot.
Bagian 1: Setup Struktur Multi-Module Maven
Langkah 1: Buat Root pom.xml
Buat konfigurasi Maven root yang mengelola semua proyek Java:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kreasipositif</groupId>
<artifactId>demo-monorepository</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Demo Monorepository</name>
<description>Nx Monorepo dengan Spring Boot dan React</description>
<modules>
<module>utility-library</module>
<module>service-a</module>
<module>service-b</module>
</modules>
<properties>
<java.version>21</java.version>
<spring-boot.version>3.2.1</spring-boot.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Fitur Utama:
<packaging>pom</packaging>: Menjadikan ini sebagai parent POM<modules>: Daftar semua subproyek Java<dependencyManagement>: Sentralisasi manajemen versi- Spring Boot 3.2.1 dengan Java 21
Bagian 2: Membuat Utility Library
Langkah 1: Buat Struktur Utility Library
mkdir -p utility-library/src/main/java/com/kreasipositif/utility
mkdir -p utility-library/src/test/java/com/kreasipositif/utility
Langkah 2: Buat utility-library/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kreasipositif</groupId>
<artifactId>demo-monorepository</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>utility-library</artifactId>
<packaging>jar</packaging>
<name>Utility Library</name>
<description>Utility bersama untuk semua services</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Langkah 3: Buat Shared Utility Classes
Buat utility-library/src/main/java/com/kreasipositif/utility/StringUtil.java:
package com.kreasipositif.utility;
public class StringUtil {
public static boolean isNullOrEmpty(String str) {
return str == null || str.trim().isEmpty();
}
public static String capitalize(String str) {
if (isNullOrEmpty(str)) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
public static String generateId(String prefix) {
return prefix + "-" + System.currentTimeMillis();
}
}
Buat utility-library/src/main/java/com/kreasipositif/utility/DateUtil.java:
package com.kreasipositif.utility;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateUtil {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static String getCurrentDateTime() {
return LocalDateTime.now().format(FORMATTER);
}
public static String formatDateTime(LocalDateTime dateTime) {
return dateTime.format(FORMATTER);
}
}
Langkah 4: Buat Konfigurasi Proyek Nx
Buat utility-library/project.json:
{
"name": "utility-library",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "utility-library/src",
"targets": {
"build": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "mvn clean install -pl utility-library -am",
"cwd": "."
}
},
"test": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "mvn test -pl utility-library",
"cwd": "."
}
}
},
"tags": ["type:library", "platform:jvm"]
}
Langkah 5: Build Utility Library
./nx build utility-library
Bagian 3: Membuat Service A (Manajemen User)
Langkah 1: Generate Proyek Spring Boot
mkdir -p service-a/src/main/java/com/kreasipositif/servicea
mkdir -p service-a/src/main/resources
mkdir -p service-a/src/test/java/com/kreasipositif/servicea
Langkah 2: Buat service-a/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kreasipositif</groupId>
<artifactId>demo-monorepository</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>service-a</artifactId>
<packaging>jar</packaging>
<name>Service A - User Management</name>
<description>Service Manajemen User</description>
<dependencies>
<!-- Shared Utility Library -->
<dependency>
<groupId>com.kreasipositif</groupId>
<artifactId>utility-library</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Langkah 3: Buat Konfigurasi Aplikasi
Buat service-a/src/main/resources/application.properties:
spring.application.name=service-a
server.port=8081
Langkah 4: Buat Main Application Class
Buat service-a/src/main/java/com/kreasipositif/servicea/ServiceAApplication.java:
package com.kreasipositif.servicea;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
}
Langkah 5: Buat User Model
Buat service-a/src/main/java/com/kreasipositif/servicea/model/User.java:
package com.kreasipositif.servicea.model;
public class User {
private String id;
private String name;
private String email;
private String phone;
private String createdAt;
public User() {}
public User(String id, String name, String email, String phone, String createdAt) {
this.id = id;
this.name = name;
this.email = email;
this.phone = phone;
this.createdAt = createdAt;
}
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getCreatedAt() { return createdAt; }
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
}
Langkah 6: Buat User Service
Buat service-a/src/main/java/com/kreasipositif/servicea/service/UserService.java:
package com.kreasipositif.servicea.service;
import com.kreasipositif.servicea.dto.CreateUserRequest;
import com.kreasipositif.servicea.model.User;
import com.kreasipositif.utility.DateUtil;
import com.kreasipositif.utility.StringUtil;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
private final List<User> users = new ArrayList<>();
public List<User> getAllUsers() {
return new ArrayList<>(users);
}
public Optional<User> getUserById(String id) {
return users.stream()
.filter(user -> user.getId().equals(id))
.findFirst();
}
public User createUser(CreateUserRequest request) {
String id = StringUtil.generateId("USER");
String createdAt = DateUtil.getCurrentDateTime();
User user = new User(
id,
request.getName(),
request.getEmail(),
request.getPhone(),
createdAt
);
users.add(user);
return user;
}
}
Langkah 7: Buat REST Controller
Buat service-a/src/main/java/com/kreasipositif/servicea/controller/UserController.java:
package com.kreasipositif.servicea.controller;
import com.kreasipositif.servicea.dto.CreateUserRequest;
import com.kreasipositif.servicea.model.User;
import com.kreasipositif.servicea.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable String id) {
return userService.getUserById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public User createUser(@RequestBody CreateUserRequest request) {
return userService.createUser(request);
}
}
Langkah 8: Buat Konfigurasi CORS
Buat service-a/src/main/java/com/kreasipositif/servicea/config/CorsConfig.java:
package com.kreasipositif.servicea.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
Langkah 9: Buat Konfigurasi Proyek Nx
Buat service-a/project.json:
{
"name": "service-a",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "service-a/src",
"targets": {
"build": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "mvn clean package -pl service-a -am",
"cwd": "."
}
},
"serve": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "mvn spring-boot:run -pl service-a",
"cwd": "."
}
},
"test": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "mvn test -pl service-a",
"cwd": "."
}
}
},
"tags": ["type:app", "platform:jvm", "api:user"]
}
Langkah 10: Test Service A
# Build service
./nx build service-a
# Run service
./nx serve service-a
# Di terminal lain, test API
curl http://localhost:8081/api/users
# Output: []
Bagian 4: Membuat Service B (Manajemen Order)
Ikuti langkah-langkah serupa untuk membuat Service B dengan fungsi manajemen order.
Langkah 1: Buat service-b/pom.xml
Mirip dengan Service A, tetapi dengan service-b sebagai artifactId dan port 8082.
Langkah 2: Buat Order Model dan Service
Buat manajemen order mirip dengan manajemen user di Service A.
Langkah 3: Buat Konfigurasi Nx
Buat service-b/project.json dengan konfigurasi port 8082.
Bagian 5: Membuat React Frontend
Langkah 1: Buat Aplikasi React
# Buat direktori frontend
mkdir -p frontend
# Inisialisasi React app dengan TypeScript
cd frontend
npx create-react-app . --template typescript
cd ..
Langkah 2: Buat Konfigurasi Environment
Buat frontend/.env:
REACT_APP_SERVICE_A_URL=http://localhost:8081
REACT_APP_SERVICE_B_URL=http://localhost:8082
Langkah 3: Buat API Service Layer
Buat frontend/src/services/userService.ts:
// Service A API - User Management
const SERVICE_A_BASE_URL =
process.env.REACT_APP_SERVICE_A_URL || "http://localhost:8081";
export interface User {
id: string;
name: string;
email: string;
phone: string;
createdAt: string;
}
export interface CreateUserRequest {
name: string;
email: string;
phone: string;
}
export const userService = {
getAllUsers: async (): Promise<User[]> => {
const response = await fetch(`${SERVICE_A_BASE_URL}/api/users`);
if (!response.ok) {
throw new Error("Gagal mengambil data users");
}
return response.json();
},
getUserById: async (id: string): Promise<User> => {
const response = await fetch(`${SERVICE_A_BASE_URL}/api/users/${id}`);
if (!response.ok) {
throw new Error("Gagal mengambil data user");
}
return response.json();
},
createUser: async (userData: CreateUserRequest): Promise<User> => {
const response = await fetch(`${SERVICE_A_BASE_URL}/api/users`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error("Gagal membuat user");
}
return response.json();
},
};
Buat frontend/src/services/orderService.ts serupa untuk Service B.
Langkah 4: Buat React Components
Buat frontend/src/components/UserList.tsx:
import React, { useEffect, useState } from "react";
import { userService, User } from "../services/userService";
import "./UserList.css";
const UserList: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [retryCount, setRetryCount] = useState(0);
useEffect(() => {
fetchUsers();
}, []);
useEffect(() => {
// Auto-retry if there's an error (backend still starting up)
if (error && retryCount < 10) {
const timer = setTimeout(() => {
setRetryCount(retryCount + 1);
fetchUsers();
}, 3000); // Retry every 3 seconds
return () => clearTimeout(timer);
}
}, [error, retryCount]);
const fetchUsers = async () => {
try {
setLoading(true);
const data = await userService.getAllUsers();
setUsers(data);
setError(null);
setRetryCount(0);
} catch (err) {
setError("Service A is starting up... Retrying automatically.");
} finally {
setLoading(false);
}
};
if (loading) return <div className="loading">Loading users...</div>;
return (
<div className="user-list">
<h2>๐ฅ User Management (Service A)</h2>
{error && <div className="error">{error}</div>}
<div className="list">
{users.length === 0 ? (
<p className="empty">No users found. Create your first user!</p>
) : (
users.map((user) => (
<div key={user.id} className="card">
<h3>{user.name}</h3>
<p>
<strong>Email:</strong> {user.email}
</p>
<p>
<strong>Phone:</strong> {user.phone}
</p>
<p className="date">
<strong>Created:</strong> {user.createdAt}
</p>
</div>
))
)}
</div>
</div>
);
};
export default UserList;
Buat frontend/src/components/OrderList.tsx serupa untuk orders.
Langkah 5: Buat Type Declaration untuk CSS
Buat frontend/src/global.d.ts:
declare module "*.css";
Langkah 6: Buat Main App Component
Update frontend/src/App.tsx:
import React from "react";
import UserList from "./components/UserList";
import OrderList from "./components/OrderList";
import "./App.css";
function App() {
return (
<div className="App">
<header className="App-header">
<h1>๐ Nx Monorepo Full-Stack Demo</h1>
<p>Spring Boot + React Integration</p>
</header>
<main className="App-main">
<UserList />
<OrderList />
</main>
</div>
);
}
export default App;
Langkah 7: Buat Konfigurasi Proyek Nx
Buat frontend/project.json:
{
"name": "frontend",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "frontend/src",
"targets": {
"serve": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "npm start",
"cwd": "frontend"
}
},
"build": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "npm run build",
"cwd": "frontend"
}
},
"test": {
"executor": "@nx/workspace:run-commands",
"options": {
"command": "npm test",
"cwd": "frontend"
}
}
},
"tags": ["type:app", "platform:web"]
}
Bagian 6: Menjalankan Full Stack
Opsi 1: Jalankan Semua Services Bersama
./nx run-many -t serve -p service-a,service-b,frontend
Catatan: Dengan logika auto-retry di komponen React, frontend akan otomatis terkoneksi setelah backend services siap (biasanya 10-30 detik).
Opsi 2: Sequential Startup Script
Buat start-all.sh:
#!/bin/bash
echo "๐ Starting all services in order..."
# Start backend services first
echo "๐ฆ Starting Service A (port 8081)..."
./nx serve service-a &
SERVICE_A_PID=$!
echo "๐ฆ Starting Service B (port 8082)..."
./nx serve service-b &
SERVICE_B_PID=$!
# Wait for backends to be ready
echo "โณ Waiting 30 seconds for backend services to start..."
sleep 30
# Start frontend
echo "๐จ Starting Frontend (port 3000)..."
./nx serve frontend &
FRONTEND_PID=$!
echo ""
echo "โ
All services started!"
echo ""
echo "Service A PID: $SERVICE_A_PID (http://localhost:8081)"
echo "Service B PID: $SERVICE_B_PID (http://localhost:8082)"
echo "Frontend PID: $FRONTEND_PID (http://localhost:3000)"
echo ""
echo "Press Ctrl+C to stop all services"
# Wait for Ctrl+C
trap "echo ''; echo '๐ Stopping all services...'; kill $SERVICE_A_PID $SERVICE_B_PID $FRONTEND_PID 2>/dev/null; exit" INT
# Keep script running
wait
Jadikan executable:
chmod +x start-all.sh
./start-all.sh
Opsi 3: Restart Backend Saja
Buat restart-backends.sh:
#!/bin/bash
echo "๐ Restarting backend services..."
# Kill existing Java processes
lsof -ti:8081 | xargs kill -9 2>/dev/null
lsof -ti:8082 | xargs kill -9 2>/dev/null
sleep 2
# Start backend services
./nx serve service-a &
./nx serve service-b &
echo "โ
Backend services restarted!"
Bagian 7: Testing Full Stack
Langkah 1: Akses Frontend
Buka browser Anda dan navigasi ke:
http://localhost:3000
Anda akan melihat:
- Section User Management menampilkan data Service A
- Section Order Management menampilkan data Service B
Langkah 2: Test API Endpoints
# Test Service A (Users)
curl http://localhost:8081/api/users
# Test Service B (Orders)
curl http://localhost:8082/api/orders
# Buat user baru
curl -X POST http://localhost:8081/api/users \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@example.com","phone":"1234567890"}'
# Buat order baru
curl -X POST http://localhost:8082/api/orders \
-H "Content-Type: application/json" \
-d '{"customerId":"USER-123","productName":"Widget","quantity":5,"unitPrice":19.99}'
Langkah 3: Verifikasi Nx Dependency Graph
./nx graph
Anda akan melihat semua empat proyek dan dependensi mereka:
service-adepends onutility-libraryservice-bdepends onutility-libraryfrontend(tidak ada dependensi langsung, tetapi mengkonsumsi kedua services via HTTP)
Bagian 8: Pattern Arsitektur Utama
1. Code Sharing via Utility Library
Baik Service A maupun Service B menggunakan shared utilities:
// Di Service A atau Service B
import com.kreasipositif.utility.StringUtil;
import com.kreasipositif.utility.DateUtil;
String id = StringUtil.generateId("USER");
String timestamp = DateUtil.getCurrentDateTime();
2. Konfigurasi CORS
Kedua backend services mengizinkan request dari React frontend:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
3. Integrasi API Type-Safe
React services dengan interface TypeScript:
export interface User {
id: string;
name: string;
email: string;
phone: string;
createdAt: string;
}
4. Auto-Retry Pattern
Frontend menangani race conditions ketika services start:
useEffect(() => {
if (error && retryCount < 10) {
const timer = setTimeout(() => {
setRetryCount(retryCount + 1);
fetchUsers();
}, 3000);
return () => clearTimeout(timer);
}
}, [error, retryCount]);
Bagian 9: Perintah Nx untuk Full Stack
Menjalankan Multiple Projects
# Run all services
./nx run-many -t serve -p service-a,service-b,frontend
# Build semua projects
./nx run-many -t build -p utility-library,service-a,service-b,frontend
# Test semua projects
./nx run-many -t test -p utility-library,service-a,service-b,frontend
Affected Commands
# Lihat affected projects
./nx show projects --affected
# Build hanya yang affected
./nx affected -t build
# Test hanya yang affected
./nx affected -t test
Dependency Graph
# Lihat full dependency graph
./nx graph
# Lihat affected graph
./nx graph --affected
Bagian 10: Masalah Umum dan Solusi
Issue 1: CORS Errors
Masalah: Frontend tidak bisa mengakses backend APIs
Solusi:
- Pastikan konfigurasi CORS ada di kedua services
- Restart backend services setelah menambah config CORS
- Gunakan
./restart-backends.sh
Issue 2: Port Sudah Digunakan
Masalah: Service gagal start
Solusi:
# Cek apa yang running di port
lsof -i :8081
# Kill process
kill -9 <PID>
Issue 3: Race Condition
Masalah: Frontend menampilkan โFailed to loadโ meski services running
Solusi: Logika auto-retry seharusnya menangani ini. Jika tidak, tunggu 30 detik setelah starting backends sebelum starting frontend.
Bagian 11: Best Practices
1. Organisasi Proyek
โ
Baik:
- service-a/ (fokus, single responsibility)
- service-b/ (fokus, single responsibility)
- utility-library/ (shared code)
- frontend/ (presentation layer)
โ Hindari:
- everything-service/ (terlalu luas)
- utils-1, utils-2/ (utilities terfragmentasi)
2. Manajemen Dependency
<!-- Root pom.xml: Sentralisasi versi -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3. Desain API
// โ
RESTful endpoints
GET /api/users // List all
GET /api/users/{id} // Get one
POST /api/users // Create
PUT /api/users/{id} // Update
DELETE /api/users/{id} // Delete
// โ Hindari
GET /api/getAllUsers
POST /api/createNewUser
4. Error Handling
// โ
Graceful degradation dengan retry
const fetchUsers = async () => {
try {
const data = await userService.getAllUsers();
setUsers(data);
setError(null);
setRetryCount(0);
} catch (err) {
setError("Service starting... Mencoba lagi otomatis.");
}
};
5. Konfigurasi Environment
# โ
Gunakan environment variables
REACT_APP_SERVICE_A_URL=http://localhost:8081
REACT_APP_SERVICE_B_URL=http://localhost:8082
# โ Hindari hardcoding
const API_URL = 'http://localhost:8081';
๐ Selamat!
Anda telah berhasil membangun aplikasi full-stack lengkap dalam Nx monorepo!
Apa yang Telah Anda Capai
- โ Membuat struktur Maven multi-module
- โ Membangun shared utility library
- โ Mengembangkan dua Spring Boot microservices
- โ Membuat React frontend dengan TypeScript
- โ Mengimplementasikan integrasi API dengan CORS
- โ Setup auto-retry pattern untuk resilience
- โ Mengelola semua projects dengan Nx
- โ Membangun arsitektur full-stack real-world
Highlights Arsitektur
- Backend: 2 Spring Boot services + 1 shared library
- Frontend: React dengan TypeScript
- Komunikasi: REST APIs dengan CORS
- Build System: Maven + npm dikelola oleh Nx
- Ports: 8081 (Service A), 8082 (Service B), 3000 (Frontend)
๐ Akan Datang di Seri 3: Advanced Code Sharing
Di bagian selanjutnya, kita akan mengeksplorasi:
Yang Akan Datang
- ๐ Advanced Shared Libraries
- ๐ Code Generation & Templates
- ๐งช Cross-Project Testing
- ๐ Monorepo Analytics
- ๐ง Custom Nx Executors
Nantikan Seri 3! ๐โจ
Sumber Daya Tambahan
Dokumentasi
Repository Contoh
Komunitas
Tentang Seri Ini
Ini adalah Bagian 2 dari Seri Nx Monorepo:
- โ Seri 1: Pengenalan Nx & Integrasi Spring Boot
- โ Seri 2: Pengembangan Full-Stack dengan Spring Boot & React (Anda di sini)
- ๐ Seri 3: Advanced Code Sharing & Libraries
- ๐ Seri 4: CI/CD & Deployment Strategies
- ๐ Seri 5: Multi-Technology Monorepo at Scale
Repository: github.com/kreasipositif/demo-monorepository-2
Kuasai pengembangan full-stack modern dengan arsitektur monorepo yang terbukti di industri. Bergabunglah dengan kursus komprehensif kami di Kreasi Positif Indonesia dan belajar dari software engineers berpengalaman.