Installing create-react-app
First of all, we’ll install create-react-app, a command line tool for creating react apps
Role model
The Role class contains an id and a name field. The name field is an enum. We’ll have a fixed set of pre-defined roles. So it makes sense to make the role name as enum.
Here is the complete code for Role class –
RoleRepository
Following is the RoleRepository interface. It contains a single method to retrieve a Role from the RoleName-
DateAudit model
All right! Let’s now define the DateAudit model. It will have a createdAt and an updatedAt field. Other domain models that need these auditing fields will simply extend this class.
We’ll use JPA’s AuditingEntityListener to automatically populate createdAt and updatedAt values when we persist an entity.
Here is the Complete DateAudit class (I’ve created a package named audit inside com.example.polls.model package to store all the auditing related models) –
To enable JPA Auditing, we’ll need to add @EnableJpaAuditing annotation to our main class or any other configuration classes.
Let’s create an AuditingConfig configuration class and add the @EnableJpaAuditing annotation to it.
We’re creating a separate class because we’ll be adding more auditing related configurations later. So it’s better to have a separate class.
We’ll keep all the configuration classes inside a package named config. Go ahead and create the config package inside com.example.polls, and then create the AuditingConfig class inside config package –
Add a react groupedit component
Create app/src/GroupEdit.js and use useEffect() to fetch the group resource with the ID from the URL.
importReact,{useEffect,useState}from'react';import{Link,useNavigate,useParams}from'react-router-dom';import{Button,Container,Form,FormGroup,Input,Label}from'reactstrap';importAppNavbarfrom'./AppNavbar';constGroupEdit=()=>{constinitialFormState={name:'',address:'',city:'',stateOrProvince:'',country:'',postalCode:''};const[group,setGroup]=useState(initialFormState);constnavigate=useNavigate();const{id}=useParams();useEffect(()=>{if(id!=='new'){fetch(`/api/group/${id}`).then(response=>response.json()).then(data=>setGroup(data));}},[id,setGroup]);consthandleChange=(event)=>{const{name,value}=event.targetsetGroup({...group,[name]:value})}consthandleSubmit=async(event)=>{event.preventDefault();awaitfetch('/api/group' (group.id?'/' group.id:''),{method:(group.id)?'PUT':'POST',headers:{'Accept':'application/json','Content-Type':'application/json'},body:JSON.stringify(group)});setGroup(initialFormState);navigate('/groups');}consttitle=<h2>{group.id?'Edit Group':'Add Group'}</h2>;return(<div><AppNavbar/><Container>{title}<FormonSubmit={handleSubmit}><FormGroup><Labelfor="name">Name</Label><Inputtype="text"name="name"id="name"value={group.name||''}onChange={handleChange}autoComplete="name"/></FormGroup><FormGroup><Labelfor="address">Address</Label><Inputtype="text"name="address"id="address"value={group.address||''}onChange={handleChange}autoComplete="address-level1"/></FormGroup><FormGroup><Labelfor="city">City</Label><Inputtype="text"name="city"id="city"value={group.city||''}onChange={handleChange}autoComplete="address-level1"/></FormGroup><divclassName="row"><FormGroupclassName="col-md-4 mb-3"><Labelfor="stateOrProvince">State/Province</Label><Inputtype="text"name="stateOrProvince"id="stateOrProvince"value={group.stateOrProvince||''}onChange={handleChange}autoComplete="address-level1"/></FormGroup><FormGroupclassName="col-md-5 mb-3"><Labelfor="country">Country</Label><Inputtype="text"name="country"id="country"value={group.country||''}onChange={handleChange}autoComplete="address-level1"/></FormGroup><FormGroupclassName="col-md-3 mb-3"><Labelfor="country">Postal Code</Label><Inputtype="text"name="postalCode"id="postalCode"value={group.postalCode||''}onChange={handleChange}autoComplete="address-level1"/></FormGroup></div><FormGroup><Buttoncolor="primary"type="submit">Save</Button>{''}<Buttoncolor="secondary"tag={Link}to="/groups">Cancel</Button></FormGroup></Form></Container></div>)};exportdefaultGroupEdit;
The useParams() hook is used to grab the ID from the URL and useNavigate() allows you to navigate back to the GroupList after adding or saving a group.
Modify app/src/App.js to import GroupEdit and specify a path to it.
Now you should be able to add and edit groups!
Adding real data
To use data objects coming from the server, you need to add a server! Doing this with Spring Boot is super simple. Inside src/main/java/tutorial/Employee.java add the following code:
@Data @Entity public class Employee { private @Id @GeneratedValue Long id; private String name; private int age; private int years; private Employee() {} public Employee(String name, int age, int years) { this.name = name; this.age = age; this.years = years; } }
This is our bean. Note: the @Data annotation is from Project Lombok.
Now, create a repository using Spring Data JPA.
public interface EmployeeRepository extends CrudRepository<Employee, Long> {}
To load data, create a CommandLineRunner implementation that uses the repository to create new records in the database.
@Component public class DatabaseLoader implements CommandLineRunner { private final EmployeeRepository repository; @Autowired public DatabaseLoader(EmployeeRepository repository) { this.repository = repository; } @Override public void run(String... strings) throws Exception { this.repository.save(new Employee("Joe Biden", 45, 5)); this.repository.save(new Employee("President Obama", 54, 8)); this.repository.save(new Employee("Crystal Mac", 34, 12)); this.repository.save(new Employee("James Henry", 33, 2)); } }
The only thing left is pulling in dependencies. Adding the following to your pom.xml will allow your repository to become a REST endpoint.
You’ll also need to include Project Lombok (which lets you ignore creating getters and setters for your beans).
And you need a database (which Spring Boot autoconfigures). You can use H2, which is embedded (i.e. in memory / won’t last a reboot).
And that’s it! If you reboot now you’ll have a functioning REST server with data.
Build a react grouplist component
React is all about components, and you don’t want to render everything in your main App, so create app/src/GroupList.js and populate it with the following JavaScript.
importReact,{useEffect,useState}from'react';import{Button,ButtonGroup,Container,Table}from'reactstrap';importAppNavbarfrom'./AppNavbar';import{Link}from'react-router-dom';constGroupList=()=>{const[groups,setGroups]=useState([]);const[loading,setLoading]=useState(false);useEffect(()=>{setLoading(true);fetch('api/groups').then(response=>response.json()).then(data=>{setGroups(data);setLoading(false);})},[]);constremove=async(id)=>{awaitfetch(`/api/group/${id}`,{method:'DELETE',headers:{'Accept':'application/json','Content-Type':'application/json'}}).then(()=>{letupdatedGroups=[...groups].filter(i=>i.id!==id);setGroups(updatedGroups);});}if(loading){return<p>Loading...</p>;}constgroupList=groups.map(group=>{constaddress=`${group.address||''}${group.city||''}${group.stateOrProvince||''}`;return<trkey={group.id}><tdstyle={{whiteSpace:'nowrap'}}>{group.name}</td><td>{address}</td><td>{group.events.map(event=>{return<divkey={event.id}>{newIntl.DateTimeFormat('en-US',{year:'numeric',month:'long',day:'2-digit'}).format(newDate(event.date))}: {event.title}</div>})}</td><td><ButtonGroup><Buttonsize="sm"color="primary"tag={Link}to={"/groups/" group.id}>Edit</Button><Buttonsize="sm"color="danger"onClick={()=>remove(group.id)}>Delete</Button></ButtonGroup></td></tr>});return(<div><AppNavbar/><Containerfluid><divclassName="float-end"><Buttoncolor="success"tag={Link}to="/groups/new">Add Group</Button></div><h3>My JUG Tour</h3><TableclassName="mt-4"><thead><tr><thwidth="20%">Name</th><thwidth="20%">Location</th><th>Events</th><thwidth="10%">Actions</th></tr></thead><tbody>{groupList}</tbody></Table></Container></div>);};exportdefaultGroupList;
Create AppNavbar.js in the same directory to establish a common UI feature between components.
Create app/src/Home.js to serve as the landing page for your app.
Also, change app/src/App.js to use React Router to navigate between components.
To make your UI a bit more spacious, add a top margin to Bootstrap’s container classes in app/src/App.css.
Configure maven to build and package react with spring boot
To build and package your React app with Maven, you can use the frontend-maven-plugin and Maven’s profiles to activate it. Add properties for versions and a <profiles> section to your pom.xml.
While you’re at it, add the active profile setting to src/main/resources/application.properties:
Create a react ui with create react app
Create React App is a command line utility that generates React projects for you. It’s a convenient tool because it also offers commands to build and optimize your project for production. It uses webpack under the covers to build everything.
Create a new project in the jugtours directory with npx.
After the app creation process completes, navigate into the app directory and install Bootstrap, cookie support for React, React Router, and Reactstrap.
You’ll use Bootstrap’s CSS and Reactstrap’s components to make the UI look better, especially on mobile phones. If you’d like to learn more about Reactstrap, see reactstrap.github.io. It has extensive documentation on Reactstrap’s various components and their use.
Add Bootstrap’s CSS file as an import in app/src/index.js.
Create an oidc app in okta
Install the Okta CLI and run okta login.
Then, run okta apps create. Select the default app name, or change it as you see fit.
Choose Web and press Enter.
Done and dusted
Just like that you have created a compliant CRUD web application with authorization and React as the front-end. I hope you found this tutorial useful! If you have any questions about integrating React, Spring Boot and Stormpath, please leave a comment.
To see a more complete React application using a Spring Boot backend see React.js and Spring Data REST.
Building Identity Management, including authentication and authorization? Try Stormpath! Our REST API and robust Java SDK support can eliminate your security risk and can be implemented in minutes. Sign up, and never build auth again!
Generating jwt token during login
Now, let us add our login and logout method that will actually generate the JWT token during login and remove the token during logout.
Index.js
This file is the main entry point of our react application –
In the above script, we simply render the App component in a DOM element with id root (This DOM element is available in public/index.html file).
Interactivity
The last thing you’ll want for your frontend is interactivity. Let’s add a delete button to see how that might work.
Add the following column to your employee render.
You’ll write the handleDelete method in a sec. After adding another heading to the employee table class, you should see buttons appear alongside each entry.
Jwt header
JWT Header has 2 parts type of token and hashing algorithm used.The JSON structure comprising these two keys are Base64Encoded. The above token has below header.
{ "typ": "JWT", "alg": "HS256" }
Jwt payload
JWT Payload contains the claims.Primarily, there are three types of claims: reserved, public, and private claims. Reserved claims are predefined claims such as iss (issuer), exp (expiration time), sub (subject), aud (audience).In private claims, we can create some custom claims such as subject, role, and others. These custom claims provides stateless authentication mechanism.
Jwt signature
Signature provides the security to the JWT token and ensures that the token is not changed on the way. In case the token is tempered in the middle, the header and payload won’t match with the signature and hence an error will be thrown while decoding the token.
There are many algorithms to encrypt the signature. For example, if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:
HMACSHA256( base64UrlEncode(header) "." base64UrlEncode(payload), secret)
Logging out
You need to be able to log out as well. This is as easy as adding a form that sends a post to /logout(which Stormpath sets up by default).
Mapping the url
If you add the following to src/main/resources/application.properties then all your REST endpoint calls will be at localhost:8080/api
spring.data.rest.basePath=/api
Calling localhost:8080/api/employees from the command line should give a list of the data you loaded.
Modify react to handle csrf and be identity-aware
You’ll need to make a few changes to your React components to make them identity-aware. The first thing you’ll want to do is modify src/index.js to wrap everything in a CookieProvider. This component allows you to read the CSRF cookie and send it back as a header.
Okta spring boot starter
Чтобы заблокировать сервер, вы можете использовать Okta’s Spring Boot Starter . Чтобы интегрировать этот стартер, добавьте следующую зависимость в server/pom.xml :
Вам также необходимо добавить раздел <dependencyManagement> чтобы обновить поддержку OAuth в Spring Security.
ПРИМЕЧАНИЕ.Существует проблема со стартером Spring Boot от Okta, когда он не работает с DevTools Spring Boot.
Теперь вам нужно настроить сервер на использование Okta для аутентификации. Для этого вам нужно будет создать приложение OIDC в Okta.
React barebones html
On to React! The most basic React page has three things: a root element, JavaScript imports, and a script tag.
React basics
As described in the thinking in react tutorial, you should start coding your application by breaking the interface down into components.
Here you’ve created two—one for a table of employees and another for an employee entry. Each component then needs a render function which describes the HTML to generate.
Here’s where the Babel compiler comes in to convert HTML code into the correct React statements. Note how the div tags are returned from the render statement.
You need to tell React to insert the parent component’s HTML into the root element. This is done using the ReactDOM.render method.
By refreshing the browser you should see the simple text element you created.
To see the HTML React inserted into the root element you can use the browser’s inspector (Ctrl-Shift-J in Chrome).
Remove restarts
Normally you’d have to restart your server each time you make a change to your front-end which is a pain. Using Spring Boot’s developer tools allows us to get around this. Add the following dependency to your POM.
Also add this configuration to your Spring Boot Maven Plugin:
Now, when you make a change to your application, or recompile any classes, it should update when you refresh your browser.
Rolename enum
Following is the RoleName enum –
Serving the front
Normally React applications are served up using Node.js, but if you’re a Java dev you’ll likely be super comfortable with this Spring Boot approach.
Initially, you’ll put the whole application in one file, index.html. To tell Spring Boot to serve it as the homepage you can use the @Controller annotation.
@Controller public class HomeController { @RequestMapping(value = "/") public String index() { return "index.html"; } }
Create an empty directory and put the above code into src/main/java/tutorial/HomeController.java. Spring Boot will then look for src/main/resources/static/index.html when you load the site.
Create a pom.xml and a Spring Boot application class. Use the following for your POM.
Put the following into src/main/java/tutorial/Application.java.
Spring security oidc
Spring Security added OIDC support in its 5.0 release. Since then, they’ve made quite a few improvements and simplified its required configuration. Add the Okta Spring Boot starter to do OIDC authentication.
This dependency is a thin wrapper around Spring Security’s OAuth and encapsulates the following dependencies:
Src/constants
I’ve defined all the global constants in src/constants/index.js file for other components use –
Tying components together
Now that you have components, let’s tie them together. You can start by trying to render hard-coded data; you’ll use the REST server later.
Above the ReactDOM command enter the following:
var EMPLOYEES = [ {name: 'Joe Biden', age: 45, years: 5}, {name: 'President Obama', age: 54, years: 8}, {name: 'Crystal Mac', age: 34, years: 12}, {name: 'James Henry', age: 33, years: 2} ];
Then add employees={EMPLOYEES} when you instantiate your table.
ReactDOM.render( <EmployeeTable employees={EMPLOYEES} />, document.getElementById('root') );
As you might expect, this passes the data into a variable named employees. Inside EmployeeTable you can access this using this.props. Let’s use that to generate a table with a row for each employee.
var EmployeeTable = React.createClass({ render: function() { var rows = []; this.props.employees.forEach(function(employee) { rows.push(<Employee employee={employee} />); }); return ( <table> <thead> <tr> <th>Name</th><th>Age</th><th>Years</th> </tr> </thead> <tbody>{rows}</tbody> </table>); } });
This instantiates a new Employee class for each element in the data (setting the employee attribute) and pushes it to an array. Then {rows} drops in the required HTML from the child class.
Now all you need do is update the render method on Employee.
var Employee = React.createClass({ render: function() { return ( <tr> <td>{this.props.employee.name}</td> <td>{this.props.employee.age}</td> <td>{this.props.employee.years}</td> </tr>); } });
You can add Bootstrap to make the table look nice. Add the following just below your script import tags:
Добавить аутентификацию с okta
Вы можете подумать: «Это довольно круто, легко понять, почему люди влюбляются в React». Есть еще один инструмент, в который вы можете влюбиться после того, как попробуете его: аутентификация с Okta! Почему окта? Потому что вы можете получить 7000 активных ежемесячных пользователей бесплатно !
Добавить поддержку pwa
Create React App имеет встроенную поддержку прогрессивных веб-приложений (PWA). Чтобы узнать, как он интегрирован, откройте client/README.md и client/README.md поиск «Создание прогрессивного веб-приложения».
Чтобы увидеть, как это работает, запустите yarn build в client каталоге. После завершения этой команды вы увидите сообщение, подобное следующему.
Очистить эти предупреждения typescript
Вы можете заметить, что консоль вашего браузера выдает некоторые предупреждения TypeScript.
|
Чтобы исправить первую проблему, измените client/src/BeerList.tsx чтобы его конструктор client/src/BeerList.tsx следующим образом:
Для второго вопроса создайте интерфейс Beer в client/src/BeerList.tsx . Поместите это рядом с другими интерфейсами наверху.
Затем измените {beers.map((beer:any)=> на {beers.map((beer:Beer)=> .
Третья проблема может быть решена путем создания нового интерфейса GiphyImageState в client/src/GiphyImage.tsx для определения свойств состояния.
После внесения этих изменений вы должны избавиться от предупреждений TypeScript.
Создайте api с помощью spring boot
ПРИМЕЧАНИЕ. Приведенные ниже инструкции по созданию API Spring Boot такие же, как и в Bootiful Development с Spring Boot и Angular . Я скопировал их ниже для вашего удобства.
Чтобы начать работу с Spring Boot, перейдите к start.vhod-v-lichnyj-kabinet.ru . В поле «Поиск зависимостей» выберите следующее:
Создать компонент beerlist
Чтобы упростить поддержку этого приложения, переместите App.tsx и рендеринг списка пива из App.tsx в его собственный компонент BeerList .
Затем измените client/src/App.tsx чтобы он содержал только оболочку и ссылку на <BeerList/> .
Создать компонент giphyimage
Чтобы он выглядел немного лучше, добавьте компонент GIPHY для получения изображений на основе имени пива. Создайте client/src/GiphyImage.tsx и поместите в него следующий код.
Измените метод render() в BeerList.tsx чтобы использовать этот компонент.
Результат должен выглядеть примерно как следующий список названий пива с изображениями.
Вы только что создали приложение React, которое взаимодействует с Spring Boot API с использованием междоменных запросов. Поздравляем!
Создать приложение oidc в okta
Войдите в свою учетную запись Okta Developer (или зарегистрируйтесь, если у вас нет учетной записи) и выберите Приложения > Добавить приложение . Нажмите « Одностраничное приложение» , нажмите « Далее» и присвойте приложению имя, которое вы запомните.
Скопируйте идентификатор клиента в файл server/src/main/resources/application.properties . Пока вы там, добавьте свойство okta.oauth2.issuer , соответствующее вашему домену Okta. Например:
Создать проект с приложением create react
Создание API кажется легкой частью в наши дни, во многом благодаря Spring Boot. В этом разделе я надеюсь показать вам, что создание пользовательского интерфейса с помощью React также довольно просто. Если вы выполните следующие шаги, вы создадите новое приложение React, получите названия пива и изображения из API и создадите компоненты для отображения данных.
Чтобы создать проект React, убедитесь, что у вас установлены Node.js , Create React App и Yarn .
В окне терминала перейдите в корень каталога spring-boot-react-example и выполните следующую команду. Эта команда создаст новое приложение React с поддержкой TypeScript.
После запуска этого процесса у вас будет новый каталог client со всеми необходимыми зависимостями. Чтобы убедиться, что все работает, перейдите в каталог client и запустите yarn start . Если все работает, вы должны увидеть следующее в вашем браузере.
До сих пор вы создали API Good-Beers и приложение React, но не создали UI для отображения списка сортов пива из вашего API. Для этого откройте client/src/App.tsx и добавьте метод componentDidMount() .
Call your spring boot api and display the results
Modify app/src/App.js to use the following code that calls /api/groups and displays the list in the UI.
TIP: I learned a lot about React Hooks from Build a CRUD App in React with Hooks by Tania Rascia.
Creating the backend application using spring boot
Let’s bootstrap the project using Spring Initialzr web tool –