简体中文 / [English]


Building a Chat App with Spring Boot + React Ep.2 - Some Details and Tips

 

This article is currently an experimental machine translation and may contain errors. If anything is unclear, please refer to the original Chinese version. I am continuously working to improve the translation.

This series documents my journey of learning Spring and React from scratch while building a small project. It's meant for reference only and is not intended as a formal tutorial. For an overview of the project, check out Ep.0.

Here are some loose ends left from the previous post — just a few tips and tricks I found helpful for improving coding efficiency and writing cleaner code.

Lombok

Spring Boot goes great with Lombok!

@Slf4j automatically adds a logger instance to your class, making logging a breeze.

For example, instead of generating all the getters and setters for an Entity via IDE, you can simply use the @Data annotation to generate them all at once. Use @Builder for a fluent builder pattern, and @AllArgsConstructor to automatically generate a constructor with all fields.

In newer versions of Spring, using @Autowired directly on fields triggers a “not recommended” warning. To fix this, simply declare your injected beans as final and add the @RequiredArgsConstructor annotation.

1
2
3
4
5
6
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserDao userDao; // This DAO will be auto-injected
// ....
}

Unified Response Format

It’s a good idea to standardize your response format early in the project. I defined a CommonResponse class to ensure all API responses follow the same structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class CommonResponse<T> {
@Schema(description = "Response code")
private int code;
@Schema(description = "Response message")
private String msg;
@Schema(description = "Response data")
private T data;

@Hidden
@JsonIgnore
// used to define http status code
private int httpCode;

public static <T> CommonResponse<T> success() {
return success(null);
}

public static <T> CommonResponse<T> success(T data) {
return success(data, 200);
}

public static <T> CommonResponse<T> success(T data, int httpCode) {
return new CommonResponse<>(0, "success", data, httpCode);
}

public static <T> CommonResponse<T> error(ErrorType type) {
return new CommonResponse<>(type.getCode(), type.getMessage(), null, type.getHttpCode());
}

public static <T> CommonResponse<T> error(ErrorType type, String msg) {
return new CommonResponse<>(type.getCode(), msg, null, type.getHttpCode());
}

public static <T> CommonResponse<T> error(BizException exception) {
return new CommonResponse<>(exception.getCode(), exception.getMessage(), null, exception.getHttpCode());
}
}

Unified Exception Handling

RESTful APIs are supposed to use HTTP status codes to indicate response states.

However, HTTP status codes alone aren’t expressive enough for detailed business logic errors. So I decided to use both HTTP status codes and custom error codes for better clarity.

I used an enum to represent possible error types, each with its own code and httpCode.

https://github.com/lyc8503/LycChat-backend/blob/master/src/main/java/site/lyc8503/chat/exception/ErrorType.java

Whenever a business error occurs in a service class, just throw new BizException(ErrorType.xxx). The upper layer catches this and extracts the error type to return a proper response to the frontend.

For catching exceptions, I implemented a custom ExceptionHandler:

https://github.com/lyc8503/LycChat-backend/blob/master/src/main/java/site/lyc8503/chat/controller/GlobalExceptionHandler.java

To handle the httpCode field in CommonResponse, I used a ControllerAdvice to intercept responses and set the HTTP status code accordingly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ControllerAdvice
public class HttpStatusCodeControllerAdvice implements ResponseBodyAdvice<CommonResponse<?>> {
/**
* Used to set http status code
*/

@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return returnType.getParameterType().isAssignableFrom(CommonResponse.class);
}

@Override
public CommonResponse<?> beforeBodyWrite(CommonResponse<?> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body != null) {
response.setStatusCode(HttpStatus.valueOf(body.getHttpCode()));
}
return body;
}
}

This article is licensed under the CC BY-NC-SA 4.0 license.

Author: lyc8503, Article link: https://blog.lyc8503.net/en/post/my-chat-2-backend-misc/
If this article was helpful or interesting to you, consider buy me a coffee¬_¬
Feel free to comment in English below o/