└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Criando uma API Rest + Banco de dados 2 | 3 | Olá! Neste tutorial pretendo explicar os pontos básicos sobre a criação 4 | de uma api REST utilizando o framework SpringBoot, assim como a integração 5 | com um Banco de Dados (Será utilizado o H2, falarei sobre). 6 | 7 | Cada um dos pontos será explicado de forma superficial, de forma que seja passado 8 | apenas o necessário para se entender como todo o sistema funciona. No tutorial 9 | será utilizada a IDE Eclipse Mars, com Java 8 e SpringBoot. 10 | 11 | Recomendo a utilização do **Postman**, que pode ser encontrado na loja de apps do Chrome, 12 | mas que roda independente do navegador. O Postman é uma ferramenta que vai nos ajudar 13 | a testar a API, realizando as requisições. O Postman será utilizado nos exemplos. 14 | 15 | Viu algum erro ou tem uma sugestão? Faça um pull-request! :D 16 | 17 | # Iniciando - Maven e POM.xml 18 | 19 | Inicialmente, você deve criar um projeto Maven e adicionar o SpringBoot como dependência, 20 | junto do módulo **starter-web**. Isso pode ser feito através deste [site](https://start.spring.io), 21 | basta adicionar a dependência e baixar o zip. 22 | 23 | Será gerado um projeto em branco, apenas com uma classe Main e uma estrutura de pacotes 24 | inicial. Você deve importar o projeto Maven em sua IDE. 25 | 26 | Verifique a classe gerada está anotada com **@SpringBootApplication**, isto faz com que o Spring 27 | identifique que esse é o ponto de início da aplicação, a classe também possui um método main, 28 | ele que será executado para a aplicação iniciar. 29 | 30 | # DevTools - Uma mão na roda 31 | 32 | Para desenvolvimento do tutorial, e, posteriormente, sua aplicação, o uso do DevTools 33 | se faz quase que obrigatório (quase, podemos fazer sem, mas tendo mais trabalho). O 34 | DevTools é uma ferramenta que, sempre que o código é modificado, re-executa a aplicação, 35 | para as modificações inseridas serem aplicadas. Isso pode ser feito manualmente, mas, é bem chato, 36 | então sugiro utilizar o DevTools. 37 | 38 | Para o utilizar, basta adicionar como dependência no pom.xml, da seguinte forma: 39 | 40 | ``` 41 | 42 | org.springframework.boot 43 | spring-boot-devtools 44 | runtime 45 | 46 | ``` 47 | 48 | Obs: Se você não estiver utilizando o DevTools, sempre pare a execução atual da aplicação 49 | antes de iniciar uma nova, pois apenas iniciar uma nova execução faz com que a última não seja 50 | terminada, fazendo com que a porta continue sendo utilizada, não permitindo que a nova execução 51 | tenha sucesso. Este erro se parece com: 52 | 53 | ``` 54 | Description: 55 | 56 | The Tomcat connector configured to listen on port 8080 failed to start. The port may already be in use or the connector may be misconfigured. 57 | ``` 58 | 59 | Se isso acontecer, basta entrar no gerenciador de processos (windows) ou fazer **ps aux | grep java** (linux) 60 | e matar o processo Java. 61 | 62 | # Criando a API 63 | ## Criando nosso primeiro endpoint - Hello Word 64 | 65 | Agora que já temos a base para aplicação pronta, podemos começar a fazer o que realmente interessa, a API Rest. 66 | Se você não sabe o que é uma API Rest, recomendo ler [isso](https://pt.stackoverflow.com/questions/45783/o-que-%C3%A9-rest-e-restful), 67 | e depois continuar. 68 | 69 | Seu próximo passo é criar uma classe, no exemplo vou utilizar a classe *InterfaceRest.java*, 70 | devemos anotar a classe com **@RestController**. Esta anotação faz o Spring identificar a classe como 71 | um controlador Rest, que podemos adicionar enpoints para nossas funcionalidades. 72 | 73 | ``` 74 | @RestController 75 | public class InterfaceRest { 76 | } 77 | ``` 78 | 79 | Dentro da classe vamos criar um método chamado helloWord, que retorna String e não tem parâmetros. 80 | O método deve ser anotado com **@RequestMapping(value = "/teste", method = RequestMethod.GET)**. 81 | A anotação **@RequestMapping** faz com que o método seja relacionado à um endpoint, ou, 82 | ao fazermos uma requisição no endpoint *"/teste"* o método seja executado. O parâmetro **value** da anotação 83 | define qual endpoint (como String) o método está relacionado. 84 | 85 | Na anotação nós temos outro parâmetro, o **method**, que indica qual método HTTP (O tipo da requisição) 86 | o endpoint e o método Java está relacionado. Logo, um método java está associado à um endpoint e um tipo 87 | de requisição. No nosso caso, o método vai aceitar requisições de **GET** para o endpoint de **"/teste"**. 88 | 89 | No corpo do método você deve fazer apenas ele retornar uma String qualquer, ex: "Oi, eu estou funcionando!". 90 | Agora, execute a aplicação, abra o Postman e realize a requisição para **localhost:8080/teste**, verifique que 91 | o retorno da response foi o que você adicionou no método Java. 92 | 93 | ``` 94 | @RestController 95 | public class InterfaceRest { 96 | 97 | @RequestMapping(value="/teste", method=RequestMethod.GET) 98 | public String helloWord() { 99 | return "Oi, eu estou funcionando!"; 100 | } 101 | } 102 | ``` 103 | 104 | Obs: Todas as classes para o projeto devem estar no mesmo package ou em packages filhos do que está a classe 105 | Main, caso contrário, o Spring não reconhecerá as classes para adicionar seus recursos corretamente à execução. 106 | 107 | ## Recursos? - Responsabilidades diferentes 108 | 109 | Agora já podemos criar endpoints que retornam informações através de uma interface Rest, mas podemos 110 | fazer isso de uma forma mais bonita do que vimos até agora, que é separando cada classe **RestController** 111 | por recurso, ex: UsuarioRest, SerieRest, DocumentoRest, fazendo com que nossa aplicação tenha endpoints do tipo 112 | 113 | ``` 114 | /usuario/cadastrar - Cadstrar um usuário - POST 115 | /usuario/regibalbo - Recuperar informações para o usuário regibalbo - GET 116 | /serie/1 - Recuoperar informações da série 1. 117 | ``` 118 | 119 | Isso poderia ser feito utilizando o caminho completo em cada método, da mesma forma que já vimos, 120 | mas também podemos realizar uma padronização, separando cada classe para um recurso, e seus métodos 121 | apenas terem o fim do endpoint para o serviço. Isso pode ser feito adicionando a anotação **@RequestMapping** 122 | na própria classe, e definindo um **value** que será a "base" para os endpoints definidos nos métodos da classe. 123 | Ex: 124 | 125 | ``` 126 | @RestController 127 | @RequestMapping(value = "/usuario") 128 | public class Teste { 129 | 130 | @RequestMapping(value = "/consulta/{nome}", method = RequestMethod.GET) 131 | public String consultarUsuario(@PathVariable String nome) { 132 | return nome; 133 | } 134 | } 135 | ``` 136 | 137 | Isso vai fazer com que o método java consultarUsuario seja executado ao realizar uma requisição de POST 138 | em **/usuario/consulta/eric**, por exemplo. 139 | 140 | **@PathVariable**: Anota um parâmetro do método que está relacionado à um endpoint. Os endpoints podem ter variáveis, ou, 141 | pedaços não fixos para a requisição, como no exemplo, utilizamos "/consulta/**{nome}**", onde a variável 142 | é definida dentro de {}, e recuperada no método se identificando o parâmetro com @PathVariable. Se realizarmos 143 | uma requisição **GET** em /usuario/consulta/eric, veremos que no método, a variável *nome* vai ter valor "eric". Teste isso. 144 | Você pode fazer o método retornar uma String, sendo essa o parâmetro, para fins de debug. 145 | 146 | ## Enviando informações para nossa API - POST, PUT 147 | 148 | Agora já sabemos como criar endpoints e os separar por recursos, vamos ver como podemos adicionar métodos que recebem 149 | **objetos** através das requisições, utilizando métodos http de POST e PUT. 150 | 151 | Primeiro, precisamos definir um objeto para ser retornado, crie uma classe qualquer, que contenha um campo de String e um 152 | inteiro, adicione gets e sets para cada campo. 153 | Obs: É necessário que os campos tenham os gets e sets. 154 | 155 | ``` 156 | public class ObjetoTeste { 157 | private String nome; 158 | private int quantidade; 159 | 160 | public String getNome() { 161 | return nome; 162 | } 163 | public void setNome(String nome) { 164 | this.nome = nome; 165 | } 166 | /**/ 167 | } 168 | ``` 169 | 170 | Uma vez que você criou sua primeira classe, implemente um método com tipo GET que retorna algum objeto 171 | que você criou. 172 | 173 | ``` 174 | @RequestMapping(value="/teste", method=RequestMethod.GET) 175 | public ObjetoTeste testeRecuperar() { 176 | ObjetoTeste obj = new ObjetoTeste(); 177 | obj.setNome("clemison"); 178 | obj.setQuantidade(12382); 179 | return obj; 180 | } 181 | ``` 182 | 183 | Agora, faça uma requisição e verifique como o objeto foi retornado, seguindo o padrão JSON, e que 184 | cada chave representa o nome do atributo para o objeto. Você vai enviar os dados para o servidor 185 | neste mesmo formato. 186 | 187 | ## Realizando o POST 188 | 189 | Agora que já sabemos como a interface Rest trata os dados, e como podemos retornar objetos completos 190 | por vez, vamos ver como fazer a api receber objetos. 191 | 192 | Crie um método com tipo POST, pode utilizar o mesmo endpoint*. 193 | 194 | * O mesmo endpoint pode suportar diversos tipos de requisição, o método java que vai ser executado fica 195 | de acordo com o tipo da requisição e o tipo definido em sua anotação. 196 | 197 | ``` 198 | @RequestMapping(value="/teste", method=RequestMethod.POST) 199 | public String testeReceber(@RequestBody ObjetoTeste objetoRecebido) { 200 | return "Recebi um objeto com " + objetoRecebido.getNome() + " - e quantidade " + objetoRecebido.getQuantidade(); 201 | } 202 | ``` 203 | 204 | A anotação **@RequestBody** identifica que o corpo da requisição deve ser transformado no objeto 205 | anotado pela mesma. 206 | 207 | No Postman, você deve mudar o tipo da requisição para POST, ir para a opção de **body**, marcar a opção **raw** 208 | e depois definir o tipo para **JSON**. Defina o body a ser enviado com um JSON no formato do seu objeto. Para 209 | o caso do exemplo: 210 | 211 | ``` 212 | { 213 | "nome": "clebinho", 214 | "quantidade": "197" 215 | } 216 | ``` 217 | 218 | Agora, faça a requisição e veja que foi retornada uma String com as informações do objeto. Criar um método de PUT 219 | funciona da mesma forma, os dois têm apenas a diferença semantica de que POST é utilizado para inserção/cadastro 220 | e o PUT é utilizado para atualização. 221 | 222 | Agora já sabemos como: 223 | * Fazer consultas de informações, com GET, e passando parâmetros opcionais no endpoint. 224 | * Enviar informações para nossa API, com POST e PUT. 225 | 226 | ## Testes - O que mais podemos fazer? 227 | 228 | Agora você pode fazer testes para verificar a API funcionando e descobrir o que mais podemos fazer, implemente o seguinte: 229 | * Um método de GET retornar uma **lista** (ou array) de objetos 230 | * Um método POST guardar o objeto enviado em um *Map*, que seja um campo estático, e depois em um GET 231 | recuperar esse objeto do *Map* (simulando persistência). Você pode guardar os objetos no Map com as chaves 232 | sendo **nome**, e recuperar através do GET passando o nome em seu endpoints, como mostrado anteriormente. 233 | * Um método de PUT que atualiza um objeto no Map, e em seguida um GET para recuperar o objeto atualizado. 234 | 235 | # Integrando com Banco de Dados H2 - Agora as coisas ficam interessantes 236 | 237 | ## H2? 238 | H2 é um banco de dados em memória, ou seja, ele simula um BD, mas sempre que a aplicação é reiniciada os dados 239 | são perdidos, diferente de um BD real, como MySQL. A vantagem de se utilizar H2 é que não precisaremos configurar 240 | um BD localmente para a máquina, nem criar tabelas e colunas. 241 | 242 | ## Driver para conexão 243 | Para se comunicar com o BD utizaremos drivers, que são interfaces que convertem os comandos Java para comandos 244 | de banco de dados. O driver que mostrarei aqui segue a especificação do JPA (Java Persistence API), que torna fácil 245 | a migração para outro BD, sem precisarmos mudar o código, apenas alterando o driver que vai ser utilizado, desde que siga 246 | o mesmo padrão. 247 | 248 | ## Adicionando JPA 249 | Adicione como dependência do pom a biblioteca do JPA: 250 | 251 | ``` 252 | 253 | org.springframework.boot 254 | spring-boot-starter-data-jpa 255 | 256 | ``` 257 | 258 | ## Adicionando driver do H2 259 | Para adicionar o driver do H2, você deve adicionar como dependência do pom: 260 | 261 | ``` 262 | 263 | com.h2database 264 | h2 265 | 266 | ``` 267 | 268 | ## Hibernate 269 | Para realizar as operações de consultar, guardar e atualizar informações, vamos utilizar o framework Hibernate, 270 | ele se comunicará com o driver do H2 de forma automática. O Hibernate é um framework robusto, que contém diversas 271 | funcionalidades e uma vasta aplicação e usabilidade, mas aqui abordaremos apenas o básico para uma aplicação o utilizar. 272 | 273 | Adicione as configurações necessárias para o Hibernate, no arquivo **/src/main/resources/application.properties**: 274 | 275 | ``` 276 | spring.datasource.url = jdbc:h2:file:~/h2/app_db;DB_CLOSE_ON_EXIT=FALSE 277 | spring.datasource.username = sa 278 | spring.datasource.password = 279 | spring.datasource.driverClassName = org.h2.Driver 280 | spring.jpa.hibernate.ddl-auto = update 281 | 282 | logging.level.org.hibernate.SQL=INFO 283 | logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 284 | ``` 285 | 286 | ## Camada de comunicação com o BD - Os DAO's 287 | Para a comunicação com o BD vamos utilizar uma camada específica, que chamaremos de DAO (Data Access Object), 288 | ela realizará todas as operações com o BD. 289 | 290 | Crie um DAO, no exemplo utilizarei o TesteDAO. Sua classe DAO deve estar anotada com **@Repository**, indicando que 291 | esta classe é um classe Repositório(de comunicação com o BD). Você também deve adicionar no DAO a anotação **@Transactional**, 292 | que vai fazer com que as operações (execuções do métodos da classe) sejam tratadas da forma de [transações](https://pt.wikipedia.org/wiki/Transa%C3%A7%C3%A3o_em_base_de_dados). 293 | Dentro de sua classe DAO você deve *injetar* o objeto do Hibernate que vai tratar as operações, da seguinte forma: 294 | 295 | ``` 296 | @Transactional 297 | @Repository 298 | public class TesteDAO { 299 | @PersistenceContext 300 | private EntityManager em; 301 | } 302 | ``` 303 | 304 | Veja sobre Injeção de dependências [aqui](https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html). 305 | A anotação **@PersistenceContext** vai adicionar (automaticamente) uma instância de EntityManager na classe, assim que ela for iniciada. 306 | O EntityManager (Gerenciador de entidades) que vai tratar as operações. 307 | 308 | Os principais métodos do EntityManager que precisamos conhecer são: 309 | * Persist(entidade) - Vai inserir uma entidade no BD 310 | * Merge(entidade) - Vai atualizar uma entidade no BD 311 | * CreateTypedQuery(String, TipoObjeto) - Cria consultas por Objetos. 312 | 313 | Agora, adicione um método na classe DAO para persistir seu *ObjetoTeste*, da seguinte forma: 314 | 315 | ``` 316 | public ObjetoTeste persisteObjeto(ObjetoTeste obj) { 317 | em.persist(obj); 318 | return obj; 319 | } 320 | ``` 321 | 322 | E um para recuperar do BD, aqui criaremos uma Query: 323 | 324 | ``` 325 | public ObjetoTeste consultaObjeto(Long id) { 326 | TypedQuery query = em.createTypedQuery("select obj from ObjetoTeste obj where obj.id = :id"); 327 | query.setParameter("id", id); 328 | return query.getSingleResult(); 329 | } 330 | ``` 331 | 332 | Note que a sintaxe da query, que é estruturada em [HQL](https://docs.jboss.org/hibernate/orm/3.5/reference/pt-BR/html/queryhql.html)(Hibernate Query Language) é parecida com a sintaxe de SQL, mas simplifica a query em alguns casos. O método **getSingleResult** 333 | vai nos retornar o objeto de resultado da consulta, de acordo com o ID passado (falarei sobre ID em seguida). 334 | 335 | Uma das diferenças do SQL se dá por não consultarmos por tabelas, mas sim por tipos de objetos, como no exemplo, 336 | estamos pegando dentre ObjetoTeste, o Hibernate consegue distinguir o que estamos buscando por os objetos 337 | persistidos terem a classe anotada por **@Entity**. 338 | 339 | Você pode adicionar parâmetros para a busca utilizando **:nomePropriedade**, e realizando a comparação 340 | como mostrado na query de exemplo acima. Para substituir os parâmetros, deve-se utilizar o método **query.setParameter**. 341 | 342 | Também é possível realizar uma consulta por outros parâmetros, seguindo o ex: 343 | 344 | ``` 345 | public ObjetoTeste consultaObjeto(Long id, String nomeObjeto) { 346 | TypedQuery query = em.createTypedQuery("select obj from ObjetoTeste obj where obj.id = :id and obj.nomeObjeto = :nomeObjeto", ObjetoTeste.class); 347 | query.setParameter("id", id); 348 | query.setParameter("nomeObjeto", nomeObjeto); 349 | return query.getSingleResult(); 350 | } 351 | ``` 352 | 353 | Deve-se apenas ter cuidado ao fazer consultas que não sejam por id porque essas podem retornar mais de um objeto, 354 | enquanto por id sempre retornará apenas 1, ou nenhum, caso o objeto não exista. 355 | 356 | ## A entidade que vai ser persistida 357 | * Lembre-se de sempre criar gets e sets para as propriedades dos objetos a serem persistidos. 358 | 359 | Para podermos persistir uma entidade, ela precisa ser devidamente identificada e distinguível de outras do mesmo 360 | tipo, fazemos isso utilizando as anotações do próprio JPA, seguindo: 361 | 362 | * @Entity - Anota a classe, identifica que esse objeto pode ser persistido no BD 363 | * @Column - Anota uma propriedade, identifica que a propriedade vai ser uma coluna do BD 364 | * @Id - Anota uma propriedade, diz que essa propriedade será o identificador para a entidade, utilizaremos identificadores do tipo Long. 365 | 366 | Na anotação @Column podemos ainda adicionar qual nome específico da Coluna e regras, como de a propriedade não poder 367 | ser nula, ex: 368 | 369 | ``` 370 | @Column(name = "nome_usuario", nullable = false) 371 | private String nomeUsuario; 372 | ``` 373 | 374 | Isso vai fazer com que não seja permitido inserir a propriedade nomeUsuario com valor *null*, assim como 375 | a propriedade será identificada no BD pela coluna **nome_usuario**. 376 | 377 | Adicione no seu objeto um campo id do tipo Long, e o anote da seguinte forma: 378 | 379 | ``` 380 | @Id 381 | @GeneratedValue(strategy = GenerationType.IDENTITY) 382 | private Long id; 383 | ``` 384 | 385 | Como mencionado acima, a anotação **@Id** vai fazer com o que campo seja o elemento que vai distinguir dois objetos para 386 | o mesmo tipo. A anotação **@GeneratedValue** vai fazer com que ele seja gerado automaticamente, e não precisemos 387 | nos preocupar em guardar dois objetos do mesmo tipo com um mesmo id. Todas as entidades que forem ser persistidas 388 | devem ter um ID. 389 | 390 | Feito isso, anote outras propriedades que deseja guardar no BD, da mesma forma que o exemplo. 391 | 392 | ## Usando o DAO 393 | Para utilizar o DAO para se recuperar e persistir os objetos, vamos utilizar a injeção de dependências, como já 394 | mencionado anteriormente, da seguinte forma: 395 | 396 | ``` 397 | @Autowired 398 | private TesteDAO dao; 399 | ``` 400 | 401 | Você deve adicionar esta injeção na sua classe Rest. Agora, faça com que algum método de POST receba o ObjetoTeste, 402 | o persista no BD, e em seguida o de GET recupere do BD, passando o id do objeto persistido. Lembre de retornar o objeto 403 | persistido no POST, para que você possa saber qual o id para a consulta. 404 | 405 | Exemplo para os endpoints de POST e GET: 406 | 407 | ``` 408 | @RequestMapping(value = "/teste/{id}", method = RequestMethod.GET) 409 | public ObjetoTeste teste(@PathVariable Long id) { 410 | return dao.consultar(id); 411 | } 412 | 413 | @RequestMapping(value = "/teste", method = RequestMethod.POST) 414 | public ObjetoTeste testeReceber(@RequestBody ObjetoTeste objetoRecebido) { 415 | return dao.inserir(objetoRecebido); 416 | } 417 | ``` 418 | 419 | * Para realizar a consulta por ID, utilize as variáveis de parâmetro do endpoint, e passe a variável para o método de consulta do DAO. 420 | * Adicione no DAO um método que atualiza uma entidade, este método vai utilizar o **EntityManager.merge**. 421 | * Adicione um método do tipo PUT que vai atualizar uma entidade no BD, lembre que a entidade passada deve ter o ID. 422 | 423 | ## Relacionando objetos 424 | E se nós quisermos dizer que o usuário vai ter ObjetoTeste? Ou uma lista de objetos? Precisamos saber como relacionar os objetos agora, 425 | 426 | Existem algumas formas de fazer isso, aqui apresentarei o formato de relação inversa, onde um usuário vai ter uma lista 427 | de objetos. Para isso, faremos o nosso ObjetoTeste ter uma propriedade chamada **idUsuario**, e sempre persistiremos 428 | o objeto com esta propriedade sendo o id de um usuário do sistema, e para a consulta nós buscaremos por *todas as séries que pertencem a este usuário*. 429 | 430 | Nosso objeto terá um novo campo: 431 | 432 | ``` 433 | @Column(name="idUsuario") 434 | private Long idUsuario; 435 | ``` 436 | 437 | E se quisermos consultar todos os objetos de um usuário, podemos fazer isso com um método no DAO desta forma: 438 | 439 | ``` 440 | public List consultarObjetosDoUsuario(Long idUsuario) { 441 | TypedQuery query = CreateTypedQuery("select obj from ObjetoTeste obj where obj.idUsuario = :idUsuario", ObjetoTeste.class) 442 | query.setParameter("idUsuario", idUsuario); 443 | return query.getResultList(); 444 | } 445 | ``` 446 | 447 | O método getResultList vai nos retornar uma lista de objetos, onde todos tem o idUsuario igual ao passado por parâmetro, 448 | ou seja, no dá todas os objetos do usuário. 449 | 450 | ## Finalizando 451 | Pronto, agora você já tem configurado no seu projeto o H2, utilizando o Hibernate, a classe Rest que se comunica com o DAO 452 | e realiza as operações com o BD, servidor finalizado! 453 | 454 | # Integrando com a parte Web - Servindo arquivos estáticos 455 | 456 | Agora que já temos nossa API funcionando com o H2, vamos fazer com que nosso servidor possa 457 | enviar os arquivos estáticos da parte Web de nosso projeto. 458 | 459 | Primeiro, você deve adicionar seus arquivos Web na pasta **src/main/webapp**. Para o tutorial, vamos adicionar 460 | apenas um arquivo **index.html** com o seguinte: 461 | 462 | ``` 463 |

Olá, estou funcionando!

464 | ``` 465 | 466 | Agora, vamos precisar adicionar uma nova classe para a configuração da forma que os arquivos 467 | estáticos serão servidos. Crie uma classe de configuração que herde de **WebMvcConfigurerAdapter** 468 | e sobrescreva o método **addResourceHandlers** da seguinte forma: 469 | 470 | ``` 471 | @Configuration 472 | public class Config extends WebMvcConfigurerAdapter { 473 | 474 | @Override 475 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 476 | registry.addResourceHandler("/*") 477 | .addResourceLocations("/*"); 478 | } 479 | } 480 | ``` 481 | 482 | Execute a aplicação e, através do navegador, entre no link **localhost:8080**, veja que seu conteúdo html 483 | agora está sendo servido com o próprio Spring. 484 | 485 | Com o **addResourceHandler**, nós fazemos com que ao se tente realizar uma requisição em qualquer ponto 486 | em **/** ele tente servir algum arquivo estático, caso exista, tentando recuperar o arquivo a partir 487 | da pasta **src/main/webapp**. 488 | 489 | Perceba que não precisamos colocar o **/index.html** especificamente na url, por o spring tentar sempre 490 | achar algum arquivo index.html para a pasta, e então o enviar. Teste adicionar outro arquivo html e o ver 491 | no navegador, mudando o final da url. 492 | 493 | Dessa forma, já podemos servir todos os arquivos que precisemos. Teste adicionar um arquivo JS e o importar no html, 494 | assim como adicionar subpastas e as acessar a partir do navegador. 495 | 496 | ## Realizando requisições do cliente para a API 497 | 498 | Para a parte Web se comunicar a sua API, basta realizar as requisições diretamente nos endpoints, sem 499 | precisar adicionar o endereço completo (como fazíamos no Postman). :D 500 | --------------------------------------------------------------------------------