2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

エンジニアになって半年くらい経つのですが、

案件先でプログラミングを全くやっておらず、先行きが見えないので少しでもアピールポイントを増やせるようにコーディングスキルを磨きたいと思い、アウトプットを兼ねて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
スクリーンショット 2023-02-04 16.11.46.png

テーブルを定義

スクリーンショット 2023-02-04 16.24.27.png

Spring Boot側

  • Model
    • データの定義
Todo.java
@Getter
@Setter
public class Todo {
    private int id;
    private String name;
    private String description;
    private boolean deleteFlag;
}
  • Controller
    • React側から来たリクエストに応じてServiceへ処理を渡す
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に処理を渡す
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を記述します

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に認識させないとエラーになる

application.properties
mybatis.type-aliases-package=com.example.apiapp.model
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メソッドを投げて処理を行う

  • メインのページ
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
  • 追加ページ
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
  • 編集ページ
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
  • ルーティング
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の連携を学ぶことができました。

udemyでtailwindcssを学んだのでReactに組み込んでスタイルを整えて、もう少し規模の大きいアプリケーションも作ってみたいと思います。

今回Spring Bootを使いましたが実務だと違う構成の方が多いんですかね?

技術や作文にまだ慣れていない部分が多く、至らぬ点が多かったとは思いますが、最後まで読んでいただき、ありがとうございました!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?