Jednoduchá implementácia elektronického obchodu s jarom

1. Prehľad našej aplikácie elektronického obchodu

V tomto tutoriále implementujeme jednoduchú aplikáciu elektronického obchodu. Vyvinieme API pomocou Spring Boot a klientsku aplikáciu, ktorá bude API využívať pomocou Angular.

Používateľ bude v zásade schopný pridávať / odstraňovať produkty zo zoznamu produktov do / z nákupného košíka a uskutočňovať objednávky.

2. Backendová časť

Na vývoj API použijeme najnovšiu verziu Spring Boot. Databázu JPA a H2 používame aj na stránku pretrvávania vecí.

Ak sa chcete dozvedieť viac o Spring Boot,mohli by ste sa pozrieť na našu sériu článkov Spring Boot a ak chcete Ak sa chcete oboznámiť s tvorbou rozhrania REST API, pozrite si ďalšiu sériu.

2.1. Maven závislosti

Poďme pripraviť náš projekt a importovať požadované závislosti do nášho pom.xml.

Budeme potrebovať niekoľko základných závislostí Spring Boot:

 org.springframework.boot spring-boot-starter-data-jpa 2.2.2.RELEASE org.springframework.boot spring-boot-starter-web 2.2.2.RELEASE 

Potom databáza H2:

 com.h2database h2 1.4.197 runtime 

A nakoniec - Jacksonova knižnica:

 com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.9.6 

Použili sme Spring Initializr na rýchle nastavenie projektu s potrebnými závislosťami.

2.2. Nastavenie databázy

Aj keď by sme s Spring Boot mohli používať databázu H2 v pamäti už po vybalení z krabice, urobíme ešte nejaké úpravy, než začneme vyvíjať naše API.

Budeme povoliť H2 konzolu v našom application.properties spis takže môžeme skutočne skontrolovať stav našej databázy a zistiť, či všetko prebieha tak, ako by sme očakávali.

Mohlo by byť tiež užitočné zaznamenávať dotazy SQL do konzoly pri vývoji:

spring.datasource.name = ecommercedb spring.jpa.show-sql = true # H2 nastavenie spring.h2.console.enabled = skutočná spring.h2.console.path = / h2-console

Po pridaní týchto nastavení budeme mať prístup k databáze na adrese // localhost: 8080 / h2-console použitím jdbc: h2: mem: ecommercedb ako JDBC URL a užívateľ sa bez hesla.

2.3. Štruktúra projektu

Projekt bude rozdelený do niekoľkých štandardných balíkov, pričom do priečinka frontend bude vložená aplikácia Angular:

├───pom.xml ├───src ├───main │ ├─── frontend │ ├───java │ │ └───com │ │ └───baeldung │ │ └───obchod │ │ │ EcommerceApplication.java │ │ ├───controler │ │ ├───dto │ │ ├───exception │ │ ├────model │ │ ├───repository │ │ └───service │ │ │ └───resources │ │ application.properties │ ├───static │ └───templates └───test └───java └───com └───baeldung └───ecommerce EcommerceApplicationIntegrationTest. java

Mali by sme poznamenať, že všetky rozhrania v balíku úložiska sú jednoduché a rozširujú Spring Data's CrudRepository, takže ich tu nebudeme zobrazovať.

2.4. Spracovanie výnimiek

Aby sme mohli správne zaobchádzať s prípadnými výnimkami, budeme potrebovať API na spracovanie výnimiek.

Viac podrobností o tejto téme nájdete v našich článkoch Spracovanie chýb pre REST s pružinou a Vlastné spracovanie chybových správ pre REST API.

Tu sa stále zameriavame na ConstraintViolationException a náš zvyk ResourceNotFoundException:

@RestControllerAdvice public class ApiExceptionHandler {@SuppressWarnings ("rawtypes") @ExceptionHandler (ConstraintViolationException.class) public ResponseEntity handle (ConstraintViolationException e) {ErrorResponse errors = new ErrorResponse (); pre (porušenie ConstraintViolation: e.getConstraintViolations ()) {ErrorItem error = new ErrorItem (); error.setCode (priestupok.getMessageTemplate ()); error.setMessage (priestupok.getMessage ()); errors.addError (chyba); } vrátiť novú ResponseEntity (chyby, HttpStatus.BAD_REQUEST); } @SuppressWarnings ("rawtypes") @ExceptionHandler (ResourceNotFoundException.class) public ResponseEntity handle (ResourceNotFoundException e) {ErrorItem error = new ErrorItem (); error.setMessage (e.getMessage ()); vrátiť novú ResponseEntity (chyba, HttpStatus.NOT_FOUND); }}

2.5. Produkty

Ak potrebujete viac informácií o vytrvalosti na jar, v sérii jarných vytrvalostí je veľa užitočných článkov.

Naša aplikácia bude podporovať iba čítanie produktov z databázy, takže najskôr musíme nejaké pridať.

Vytvorme jednoduchý Výrobok trieda:

@Entity public class Product {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) private Long id; @NotNull (message = "Názov produktu je povinný.") @Basic (voliteľné = nepravdivé) súkromné ​​meno reťazca; súkromná dvojitá cena; private String pictureUrl; // všetky kontruktory argumentov // štandardné getre a setre}

Aj keď používateľ nebude mať možnosť pridávať produkty prostredníctvom aplikácie, podporíme uloženie produktu v databáze, aby bolo možné vopred vyplniť zoznam produktov.

Pre naše potreby bude stačiť jednoduchá služba:

@Service @Transactional verejná trieda ProductServiceImpl implementuje ProductService {// productRepository injektor konštruktora @Override public Iterable getAllProducts () {return productRepository.findAll (); } @Override public Product getProduct (long id) {return productRepository .findById (id) .orElseThrow (() -> new ResourceNotFoundException ("Product not found")); } @Override public Product save (Produktový produkt) {return productRepository.save (produkt); }}

Jednoduchý radič vybaví žiadosti o získanie zoznamu produktov:

@RestController @RequestMapping ("/ api / products") verejná trieda ProductController {// productService injektor konštruktora @GetMapping (value = {"", "/"}) public @NotNull Iterable getProducts () {return productService.getAllProducts (); }}

Všetko, čo teraz potrebujeme, aby sme používateľovi sprístupnili zoznam produktov - je skutočne vložiť niektoré produkty do databázy. Preto využijeme CommandLineRunner trieda na výrobu a Bean v našej hlavnej triede aplikácií.

Týmto spôsobom vložíme produkty do databázy počas spustenia aplikácie:

Bežec @Bean CommandLineRunner (ProductService productService) {return args -> {productService.save (...); // viac produktov}

Ak teraz spustíme našu aplikáciu, mohli by sme získať zoznam produktov pomocou // localhost: 8080 / api / products. Tiež, ak pôjdeme na // localhost: 8080 / h2-console a prihlásime sa, uvidíme, že existuje tabuľka s názvom VÝROBOK s produktmi, ktoré sme práve pridali.

2.6. Objednávky

Na strane API musíme povoliť POST požiadavky, aby sme uložili objednávky, ktoré urobí koncový užívateľ.

Najprv vytvoríme model:

@Entity @Table (name = "commands") verejná trieda Order {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) private Long id; @JsonFormat (pattern = "dd / MM / rrrr") private LocalDate dateCreated; stav súkromného reťazca; @JsonManagedReference @OneToMany (mappedBy = "pk.order") @Valid private List orderProducts = new ArrayList (); @Transient public Double getTotalOrderPrice () {dvojnásobná suma = 0D; Zoznam orderProducts = getOrderProducts (); pre (OrderProduct op: orderProducts) {sum + = op.getTotalPrice (); } vratna suma; } @Transient public int getNumberOfProducts () {return this.orderProducts.size (); } // štandardní zakladatelia a zakladatelia}

Mali by sme si tu všimnúť niekoľko vecí. Jednou z najpozoruhodnejších vecí je určite nezabudnite zmeniť predvolený názov našej tabuľky. Keďže sme pomenovali triedu objednať, štandardne uvedená tabuľka OBJEDNAŤ by mali byť vytvorené. Ale pretože sa jedná o vyhradené slovo SQL, pridali sme @Table (meno = „objednávky“) vyhnúť sa konfliktom.

Ďalej máme dve @ Prechodné metódy, ktoré vrátia celkovú sumu za danú objednávku a počet produktov v nej. Oba predstavujú vypočítané údaje, takže nie je potrebné ich ukladať do databázy.

Nakoniec tu máme a @OneToMany vzťah predstavujúci podrobnosti objednávky. Na to potrebujeme ďalšiu triedu entít:

@Entity verejná trieda OrderProduct {@EmbeddedId @JsonIgnore private OrderProductPK pk; @Column (nullable = false) súkromné ​​celočíselné množstvo; // predvolený konštruktor public OrderProduct (objednávka objednávky, produkt produktu, celočíselné množstvo) {pk = nový OrderProductPK (); pk.setOrder (objednávka); pk.setProduct (produkt); this.quantity = množstvo; } @Transient public Product getProduct () {return this.pk.getProduct (); } @Transient public Double getTotalPrice () {return getProduct (). GetPrice () * getQuantity (); } // štandardní getri a nastavovatelia // metódy hashcode () a equals ()}

Máme zložený primárny kľúčtu:

@Embeddable verejná trieda OrderProductPK implementuje Serializable {@JsonBackReference @ManyToOne (voliteľné = false, fetch = FetchType.LAZY) @JoinColumn (name = "order_id") súkromná objednávka objednávky; @ManyToOne (voliteľné = false, fetch = FetchType.LAZY) @JoinColumn (name = "product_id") súkromný produktový produkt; // štandardné getre a settery // metódy hashcode () a equals ()}

Tieto triedy nie sú ničím príliš komplikovaným, ale mali by sme si to uvedomiť v OrderProduct triedy sme dali @JsonIgnore na primárnom kľúči. To preto, lebo nechceme serializovať objednať súčasť primárneho kľúča, pretože by bol nadbytočný.

Potrebujeme iba Výrobok ktoré sa majú používateľovi zobraziť, takže máme prechodný jav getProduct () metóda.

Ďalej potrebujeme jednoduchú implementáciu služby:

@Service @Transactional public class OrderServiceImpl implementuje OrderService {// orderRepository injektor konštruktora @Override public Iterable getAllOrders () {return this.orderRepository.findAll (); } @Override public Objednávka create (Objednávka objednávky) {order.setDateCreated (LocalDate.now ()); vrátiť this.orderRepository.save (objednávka); } @Override public void update (objednávka objednávky) {this.orderRepository.save (objednávka); }}

A radič mapovaný na / api / objednávky narábať s objednať žiadosti.

Najdôležitejšie je vytvoriť() metóda:

@PostMapping public ResponseEntity create (formulár @RequestBody OrderForm) {List formDtos = form.getProductOrders (); validateProductsExistence (formDtos); // vytvorenie logiky objednávky // vyplnenie objednávky produktmi order.setOrderProducts (orderProducts); this.orderService.update (objednávka); Reťazec uri = ServletUriComponentsBuilder .fromCurrentServletMapping () .path ("/ commands / {id}") .buildAndExpand (order.getId ()) .toString (); Hlavičky HttpHeaders = nové HttpHeaders (); headers.add ("Umiestnenie", uri); vrátiť novú ResponseEntity (objednávka, hlavičky, HttpStatus.CREATED); }

Po prvé, prijímame zoznam výrobkov s ich zodpovedajúcim množstvom. Potom, skontrolujeme, či existujú všetky produkty v databáze a potom vytvorte a uložte novú objednávku. Ponechávame si odkaz na novovytvorený objekt, aby sme k nemu mohli pridať podrobnosti objednávky.

Nakoniec vytvoríme hlavičku „Umiestnenie“.

Podrobná implementácia sa nachádza v úložisku - odkaz na ňu je uvedený na konci tohto článku.

3. Frontend

Teraz, keď máme vytvorenú aplikáciu Spring Boot, je čas sa pohnúť uhlová časť projektu. Aby sme to mohli urobiť, najskôr si musíme nainštalovať Node.js s NPM a potom s Angular CLI, rozhraním príkazového riadku pre Angular.

Inštalácia oboch je skutočne jednoduchá, ako sme videli v oficiálnej dokumentácii.

3.1. Nastavenie uhlového projektu

Ako sme už spomenuli, použijeme Uhlová CLI vytvoriť našu aplikáciu. Aby sme zjednodušili prácu a mali všetko na jednom mieste, necháme našu aplikáciu Angular vo vnútri / src / main / frontend priečinok.

Aby sme ho vytvorili, musíme otvoriť terminál (alebo príkazový riadok) v / src / main priečinok a spustiť:

nový frontend

Týmto sa vytvoria všetky súbory a priečinky, ktoré potrebujeme pre našu aplikáciu Angular. V spise pakage.json, môžeme skontrolovať, ktoré verzie našich závislostí sú nainštalované. Tento tutoriál je založený na Angular v6.0.3, ale staršie verzie by mali robiť svoju prácu, minimálne verzie 4.3 a novšie (HttpClient ktoré tu používame, sme uviedli v Angular 4.3).

Mali by sme si to všimnúť spustíme všetky naše príkazy z / frontend priečinok pokiaľ nie je uvedené inak.

Toto nastavenie stačí na spustenie aplikácie Angular spustením ng slúžiť príkaz. V predvolenom nastavení beží // localhost: 4200 a ak tam teraz pôjdeme, uvidíme načítanú základnú uhlovú aplikáciu.

3.2. Pridáva sa Bootstrap

Predtým, ako začneme s vytváraním vlastných komponentov, najskôr si doplňte Bootstrap k nášmu projektu, aby sme mohli naše stránky vyzerať pekne.

Potrebujeme iba niekoľko vecí, aby sme to dosiahli. Najprv musímespustite príkaz na jeho inštaláciu:

npm install --save bootstrap

a potom povedať Angular, aby to skutočne použil. Na tento účel musíme otvoriť súbor src / main / frontend / angular.json a pridať node_modules / bootstrap / dist / css / bootstrap.min.css pod „Štýly“ nehnuteľnosť. A to je všetko.

3.3. Súčasti a modely

Predtým, ako začneme vytvárať komponenty pre našu aplikáciu, najskôr sa pozrime, ako bude naša aplikácia skutočne vyzerať:

Teraz vytvoríme základnú súčasť s názvom elektronický obchod:

ng g c elektronický obchod

Týmto sa vytvorí náš komponent vo vnútri / frontend / src / app priečinok. Načítame ho pri štarte aplikáciezahrni todo app.component.html:

Ďalej vytvoríme ďalšie komponenty vo vnútri tejto základnej komponenty:

ng g c / elektronický obchod / produkty ng g c / elektronický obchod / objednávky ng g c / elektronický obchod / nákupný košík

Všetky tieto priečinky a súbory sme určite mohli vytvoriť ručne, ak je to preferované, ale v takom prípade by sme to potrebovali nezabudnite tieto komponenty zaregistrovať v našom AppModule.

Na ľahkú manipuláciu s našimi údajmi budeme tiež potrebovať niektoré modely:

exportná trieda Produkt {id: číslo; meno: reťazec; cena: počet; pictureUrl: string; // konštruktor všetkých argumentov}
trieda exportu ProductOrder {product: Product; množstvo: počet; // konštruktor všetkých argumentov}
trieda exportu ProductOrders {productOrders: ProductOrder [] = []; }

Posledný spomínaný model sa zhoduje s naším Objednávka na backende.

3.4. Základný komponent

Na vrchole našej elektronický obchod komponent, umiestnime navbar s odkazom Domov vpravo:

 Baeldung Ecommerce 
  • Domov (aktuálny)

Odtiaľto načítame aj ďalšie komponenty:

Mali by sme mať na pamäti, že aby sme videli obsah z našich komponentov, pretože používame navbar triedy, musíme do CSS pridať nejaké CSS app.component.css:

. kontajner {padding-top: 65px; }

Poďme sa pozrieť na .ts pred komentovaním najdôležitejších častí:

@Component ({selector: 'app-ecommerce', templateUrl: './ecommerce.component.html', styleUrls: ['./ecommerce.component.css']})) exportná trieda EcommerceComponent implementuje OnInit {private collapsed = true; orderFinished = false; @ViewChild ('productsC') productsC: ProductsComponent; @ViewChild ('shoppingCartC') shoppingCartC: ShoppingCartComponent; @ViewChild ('commandsC') commandsC: OrdersComponent; toggleCollapsed (): void {this.collapsed =! this.collapsed; } finishOrder (orderFinished: boolean) {this.orderFinished = orderFinished; } reset () {this.orderFinished = false; this.productsC.reset (); this.shoppingCartC.reset (); this.ordersC.paid = false; }}

Ako vidíme, klikanie na ikonu Domov odkaz resetuje podradené komponenty. Potrebujeme prístup k metódam a poľu vo vnútri detských komponentov od rodiča, preto si ponechávame odkazy na deti a používame ich vo vnútri reset () metóda.

3.5. Služba

Aby za súrodencov komponenty na vzájomnú komunikáciua na načítanie / odoslanie údajov z / do nášho API, budeme musieť vytvoriť službu:

@Injectable () exportná trieda EcommerceService {private productsUrl = "/ api / products"; súkromné ​​objednávkyUrl = "/ api / objednávky"; private productOrder: ProductOrder; súkromné ​​objednávky: ProductOrders = new ProductOrders (); private productOrderSubject = nový predmet (); private commandsSubject = new Subject (); private totalSubject = new Subject (); súkromné ​​celkom: počet; ProductOrderChanged = this.productOrderSubject.asObservable (); OrdersChanged = this.ordersSubject.asObservable (); TotalChanged = this.totalSubject.asObservable (); konštruktor (súkromný http: HttpClient) {} getAllProducts () {vrátiť this.http.get (this.productsUrl); } saveOrder (objednávka: ProductOrders) {vrátiť this.http.post (this.ordersUrl, objednávka); } // vyhľadávače a nastavovače pre zdieľané polia}

Ako sme si mohli všimnúť, sú tu pomerne jednoduché veci. Robíme GET a POST požiadavky na komunikáciu s API. Tiež robíme pozorovateľné údaje, ktoré musíme zdieľať medzi komponentmi, aby sme si ich mohli neskôr predplatiť.

Napriek tomu musíme upozorniť na jednu vec, ktorá sa týka komunikácie s API. Keby sme teraz spustili aplikáciu, dostali by sme 404 a nezískali by sme žiadne údaje. Dôvod je ten, že keďže používame relatívne adresy URL, spoločnosť Angular sa predvolene pokúsi uskutočniť hovor // localhost: 4200 / api / products a naša backendová aplikácia beží ďalej localhost: 8080.

Adresy URL by sme mohli napevno zakódovať na localhost: 8080, samozrejme, ale to nie je niečo, čo by sme chceli robiť. Namiesto toho pri práci s rôznymi doménami by sme mali vytvoriť súbor s názvom proxy-conf.json v našom / frontend priečinok:

{"/ api": {"target": "// localhost: 8080", "secure": false}}

A potom musíme otvorené balíček.json a zmeniť skripty.štart nehnuteľnosť tak, aby zodpovedala:

"scripts": {... "start": "ng serve --proxy-config proxy-conf.json", ...}

A teraz by sme mali nezabudnite aplikáciu spustiť pomocou npm štart namiesto toho ng slúžiť.

3.6. Produkty

V našom ProductsComponent, vložíme službu, ktorú sme vytvorili skôr, a načítame zoznam produktov z API a transformujeme ich do zoznamu ProductOrders pretože chceme ku každému produktu pripojiť pole množstva:

trieda exportu ProductsComponent implementuje OnInit {productOrders: ProductOrder [] = []; products: Product [] = []; selectedProductOrder: ProductOrder; súkromné ​​shoppingCartOrders: ProductOrders; sub: Predplatné; productSelected: boolean = false; konštruktor (súkromný ecommerceService: EcommerceService) {} ngOnInit () {this.productOrders = []; this.loadProducts (); this.loadOrders (); } loadProducts () {this.ecommerceService.getAllProducts () .subscribe ((products: any []) => {this.products = products; this.products.forEach (product => {this.productOrders.push (new ProductOrder ( produkt, 0));})}, (chyba) => console.log (chyba)); } loadOrders () {this.sub = this.ecommerceService.OrdersChanged.subscribe (() => {this.shoppingCartOrders = this.ecommerceService.ProductOrders;}); }}

Potrebujeme tiež možnosť pridať produkt do nákupného košíka alebo z neho jeden odstrániť:

addToCart (objednávka: ProductOrder) {this.ecommerceService.SelectedProductOrder = objednávka; this.selectedProductOrder = this.ecommerceService.SelectedProductOrder; this.productSelected = true; } removeFromCart (productOrder: ProductOrder) {let index = this.getProductIndex (productOrder.product); if (index> -1) {this.shoppingCartOrders.productOrders.splice (this.getProductIndex (productOrder.product), 1); } this.ecommerceService.ProductOrders = this.shoppingCartOrders; this.shoppingCartOrders = this.ecommerceService.ProductOrders; this.productSelected = false; }

Nakoniec vytvoríme a resetovať(), ktorú sme spomenuli v časti 3.4:

reset () {this.productOrders = []; this.loadProducts (); this.ecommerceService.ProductOrders.productOrders = []; this.loadOrders (); this.productSelected = false; }

Prejdeme sa zoznamom produktov v našom súbore HTML a zobrazíme ich používateľovi:

{{order.product.name}}

$ {{order.product.price}}

3.8. Objednávky

Budeme všetko udržiavať čo najjednoduchšie a v OrdersComponent simulovať platenie nastavením vlastnosti na true a uložením objednávky do databázy. Môžeme skontrolovať, či sú objednávky uložené buď prostredníctvom konzola h2 alebo úderom // localhost: 8080 / api / objednávky.

Potrebujeme EcommerceService tu tiež za účelom získania zoznamu produktov z nákupného košíka a celkovej sumy za našu objednávku:

trieda exportu OrdersComponent implementuje OnInit {objednávky: ProductOrders; celkový počet; platené: boolean; sub: Predplatné; konštruktor (súkromný ecommerceService: EcommerceService) {this.orders = this.ecommerceService.ProductOrders; } ngOnInit () {this.paid = false; this.sub = this.ecommerceService.OrdersChanged.subscribe (() => {this.orders = this.ecommerceService.ProductOrders;}); this.loadTotal (); } pay () {this.paid = true; this.ecommerceService.saveOrder (this.orders) .subscribe (); }}

A nakoniec musíme používateľovi zobraziť informácie:

OBJEDNAŤ

  • {{order.product.name}} - $ {{order.product.price}} x {{order.quantity}} ks.

Celková suma: {{total}} USD

Zaplať Blahoželáme! Objednávku ste úspešne vykonali.

4. Zlúčenie jarných topánok a uhlových aplikácií

Dokončili sme vývoj oboch našich aplikácií a je asi jednoduchšie, aby sme ich vyvíjali samostatne, ako sme to dosiahli. Ale vo výrobe by bolo oveľa pohodlnejšie mať jednu aplikáciu, takže teraz tieto dve zlúčime.

To, čo tu chceme robiť, je vytvoriť aplikáciu Angular, ktorá volá Webpack, aby zhromaždila všetky aktíva a vložila ich do / resources / static adresár aplikácie Spring Boot. Takto môžeme len spustiť aplikáciu Spring Boot a otestovať našu aplikáciu, zabaliť to všetko a nasadiť ako jednu aplikáciu.

Aby sme to umožnili, musíme otvorené 'balíček.json‘Opäť pridať niekoľko nových skriptov po skriptov.stavať:

"postbuild": "npm run deploy", "predeploy": "rimraf ../resources/static/ && mkdirp ../resources/static", "deploy": "copyfiles -f dist / ** ../resources/ statický ",

Používame niektoré balíčky, ktoré nemáme nainštalované, tak si ich nainštalujme:

npm install --save-dev rimraf npm install --save-dev mkdirp npm install --save-dev copyfiles

The rimraf príkaz pozrie adresár a vytvorí nový adresár (vlastne ho vyčistí), zatiaľ čo copyfiles skopíruje súbory z distribučného priečinka (kde Angular umiestňuje všetko) do nášho statický priečinok.

Teraz už len musíme bežať NPM spustiť zostavenie príkaz a toto by malo spustiť všetky tieto príkazy a konečným výstupom bude naša zabalená aplikácia v statickom priečinku.

Potom spustíme našu aplikáciu Spring Boot na porte 8080, sprístupníme ju tam a použijeme aplikáciu Angular.

5. Záver

V tomto článku sme vytvorili jednoduchú aplikáciu elektronického obchodu. Vytvorili sme API na backende pomocou Spring Boot a potom sme ho spotrebovali v našej frontendovej aplikácii vytvorenej v Angular. Predviedli sme, ako vyrobiť komponenty, ktoré potrebujeme, zabezpečiť ich vzájomnú komunikáciu a načítanie / odosielanie údajov z / do API.

Nakoniec sme si ukázali, ako spojiť obe aplikácie do jednej zabalenej webovej aplikácie vo vnútri statického priečinka.

Kompletný projekt, ktorý sme opísali v tomto článku, nájdete ako vždy v projekte GitHub.


$config[zx-auto] not found$config[zx-overlay] not found