--- Nx Monorepo Seri 2: Pengembangan Full-Stack dengan Spring Boot & React
Beranda โ†’ Blog โ†’ Nx Monorepo Seri 2: Pengembangan Full-Stack dengan Spring Boot & React

Nx Monorepo Seri 2: Pengembangan Full-Stack dengan Spring Boot & React

Bangun aplikasi full-stack lengkap dengan microservices Spring Boot, shared libraries, dan frontend React dalam Nx monorepo - pelajari arsitektur yang digunakan perusahaan teknologi modern.

Nx Monorepo Seri 2: Pengembangan Full-Stack dengan Spring Boot & React

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-a depends on utility-library
  • service-b depends on utility-library
  • frontend (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:

  1. Pastikan konfigurasi CORS ada di kedua services
  2. Restart backend services setelah menambah config CORS
  3. 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.