未経験駆け出しエンジニアはスキルをつけたい

# Spring Boot・React・MysqlでTodoアプリを作ってみた


エンジニアになって半年くらい経つのですが、<br>
案件先でプログラミングを全くやっておらず、先行きが見えないので少しでもアピールポイントを増やせるようにコーディングスキルを磨きたいと思い、アウトプットを兼ねてSpring BootとReactを使ってTodoアプリを作ってみました。

※デザインは付けておらずSpring BootとReactとの連携を試してみただけなのでレベル感が底辺なので予めご了承ください。

## 現在のスキル感
- エンジニア歴半年
    - shellコマンドを叩くだけのお仕事
- 研修でJava・SpringMVCを学んだ
- 独学でJavaScript・Reactの勉強中

## 使用したもの
- Spring Boot 2.7.8(Java 11)
    - maven
    - Mybatis
- React 18.2.0
    - react-router-dom
    - axios
- Mysql

## 主な機能
- Todo
    - 一覧表示
    - 編集
    - 削除
    - 登録

## 完成品
![スクリーンショット 2023-02-04 16.09.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523157/a98290f8-1de9-9bfc-80fe-187050fe342b.png)
![スクリーンショット 2023-02-04 16.11.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523157/2ef9627b-7ebd-d401-c969-bc067f17f5d9.png)

## テーブルを定義
![スクリーンショット 2023-02-04 16.24.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523157/1c80ad17-f9ed-8b31-304f-60e509d4ed52.png)

## Spring Boot側
- Model
    - データの定義
```java:Todo.java
@Getter
@Setter
public class Todo {
    private int id;
    private String name;
    private String description;
    private boolean deleteFlag;
}
```
- Controller
    - React側から来たリクエストに応じてServiceへ処理を渡す
```java:TodoController
@RestController
@CrossOrigin("http://localhost:3000")
@RequestMapping("/todo")
public class TodoController {

    @Autowired
    TodoService todoService;

    @GetMapping("/list")
    List<Todo> getTodos() {
        return todoService.findAll();
    }

    @PostMapping("/create")
    int createTodo(@RequestBody Todo newTodo) {
        return todoService.createTodo(newTodo);
    }

    @GetMapping("/edit/{id}")
    Todo editTodo(@PathVariable Long id) {
        return todoService.findById(id);
    }

    @PutMapping("/edit/{id}")
    int saveTodo(@RequestBody Todo updateTodo, @PathVariable Long id) {
        return todoService.saveTodo(updateTodo);
    }

    @DeleteMapping("delete/{id}")
    int deleteTodo(@PathVariable Long id) {
        return todoService.deleteById(id);
    }
}
```
- Service
    - Mapperに処理を渡す
```java:TodoService
@Service
@Transactional
public class TodoService {
    @Autowired
    TodoMapper todoMapper;
    public List<Todo> findAll() {
        return todoMapper.findAll();
    }

    public int createTodo(Todo newTodo) {
        return todoMapper.createTodo(newTodo);
    }

    public Todo findById(Long id) {
        return todoMapper.findById(id);
    }

    public int saveTodo(Todo updateTodo) {
        return todoMapper.saveTodo(updateTodo);
    }

    public int deleteById(Long id) {
        return todoMapper.deleteTodo(id);
    }
}
```
- DB操作
    - Serviceから渡された情報をもとにDB操作を行う
     
Mybatisを使っているので、「TodoMapper.xml」にSQLを記述します
```java:TodoMapper.java
@Mapper
public interface TodoMapper {
    List<Todo> findAll();

    int createTodo(Todo newTodo);

    Todo findById(Long id);

    int saveTodo(Todo updateTodo);

    int deleteTodo(Long id);
}
```
※「application.properties」でmodelへのパスを指定してMybatisに認識させないとエラーになる
```java:application.properties
mybatis.type-aliases-package=com.example.apiapp.model
```
```java:TodoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTDMapper3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.apiapp.repository.TodoMapper">
  <select id="findAll" resultType="Todo">
    SELECT
      *
    FROM
      todo
  </select>
  <insert id="createTodo" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    INSERT INTO todo
      (name, description)
    VALUES
      (#{name}, #{description})
  </insert>
  <select id="findById" resultType="Todo">
    SELECT
      *
    FROM
      todo
    WHERE
      id = #{id}
  </select>
  <update id="saveTodo">
    UPDATE
      todo
    SET
      name = #{name},
      description = #{description}
    WHERE
      id = #{id}
  </update>
  <delete id="deleteTodo">
    DELETE FROM
      todo
    WHERE
      id = #{id}
  </delete>
</mapper>
```

## React側
基本的にaxiosでhttpメソッドを投げて処理を行う
- メインのページ
```java:Home.js
const Home = () => {
  const [todos, setTodos] = useState([]);
  const { id } = useParams();

  useEffect(() => {
    loadTodos()
  }, [])

  const loadTodos = async () => {
    const result = await axios.get("http://localhost:8080/todo/list")
    setTodos(result.data)
  }

  const deleteTodo = async (id) => {
    const res = window.confirm('本当に削除しますか？')
    if (res) {
      await axios.delete(`http://localhost:8080/todo/delete/${id}`)
      loadTodos() 
    }
  }

  return (
    <div>
      <table border="1">
        <thead>
          <tr>
            <th>id</th>
            <th>名前</th>
            <th>説明</th>
            <th>アクション</th>
          </tr>
        </thead>
        <tbody>
          {
            todos.map((todo, index) => (
              <tr>
                <th key={index}>{index + 1}</th>
                <td>{todo.name}</td>
                <td>{todo.description}</td>
                <td>
                  <Link to={`edittodo/${todo.id}`}>編集</Link>
                  <button onClick={() => deleteTodo(todo.id)}>削除</button>
                </td>
              </tr>
            ))
          }
        </tbody>
      </table>
      <Link to={"/addtodo"}>追加</Link>
    </div>
  )
}

export default Home
```
- 追加ページ
```java:AddTodo.js
const AddTodo = () => {
  let navigate = useNavigate();

  const [todo, setTodo] = useState({
    name: "",
    description: ""
  })

  const { name, description } = todo

  const onInputChange = (e) => {
    setTodo({...todo, [e.target.name]: e.target.value})
  }

  const onSubmit = async (e) => {
    e.preventDefault()
    await axios.post("http://localhost:8080/todo/create", todo)
    navigate("/")
  }

  return (
    <div>
      <h1>Todo追加</h1>
      <from onSubmit={(e) => onSubmit(e)}>
        <div>
          <label htmlFor="Name">名前</label>
          <input
            type="text"
            name='name'
            value={name}
            onChange={(e) => onInputChange(e)}
          />
        </div>
        <br />
        <div>
          <label htmlFor="Name">説明</label>
          <textarea
            type="text"
            name='description'
            value={description}
            onChange={(e) => onInputChange(e)}
          />
        </div>
        <br />
        <div>
          <button type='submit' onClick={onSubmit}>登録</button>
          <Link to="/">キャンセル</Link>
        </div>
      </from>
    </div>
  )
}

export default AddTodo
```
- 編集ページ
```java:EditTodo.js
const EditTodo = () => {
  let navigate = useNavigate();
  const { id } = useParams();

  const [todo, setTodo] = useState({
    name: "",
    description: ""
  })

  const { name, description } = todo

  const onInputChange = (e) => {
    setTodo({...todo, [e.target.name]: e.target.value})
  }

  useEffect(() => {
    loadTodo()
  }, [])

  const onSubmit = async (e) => {
    e.preventDefault();
    await axios.put(`http://localhost:8080/todo/edit/${id}`, todo);
    navigate("/");
  }

  const loadTodo = async () => {
    const result = await axios.get(`http://localhost:8080/todo/edit/${id}`);
    setTodo(result.data);
  }

  return (
    <div>
      <h1>Todo編集</h1>
      <from onSubmit={(e) => onSubmit(e)}>
        <div>
          <label htmlFor="Name">名前</label>
          <input
            type="text"
            name='name'
            value={name}
            onChange={(e) => onInputChange(e)}
          />
        </div>
        <br />
        <div>
          <label htmlFor="Name">説明</label>
          <textarea
            type="text"
            name='description'
            value={description}
            onChange={(e) => onInputChange(e)}
          />
        </div>
        <br />
        <div>
          <button type='submit' onClick={onSubmit}>更新</button>
          <Link to="/">キャンセル</Link>
        </div>
      </from>
    </div>
  )
}

export default EditTodo
```
- ルーティング
```java:App.js
function App() {
  return (
    <div className='App'>
      <Router>
        <Routes>
          <Route exact path='/' element={<Home />} />
          <Route exact path='/addtodo' element={<AddTodo />} />
          <Route exact path='/edittodo/:id' element={<EditTodo />} />
        </Routes>
      </Router>
    </div>
  );
}

export default App;
```
## これから
簡単ではありますがSpring Boot・React・Mysqlの連携を学ぶことができました。<br>
udemyでtailwindcssを学んだのでReactに組み込んでスタイルを整えて、もう少し規模の大きいアプリケーションも作ってみたいと思います。<br>
今回Spring Bootを使いましたが実務だと違う構成の方が多いんですかね？<br>
技術や作文にまだ慣れていない部分が多く、至らぬ点が多かったとは思いますが、最後まで読んでいただき、ありがとうございました！