Exercise - Create an API
In this exercise, you move the static data from the app into a mock server, and then you use the actual server.
Prepare code to fetch data from mock server
At this point, you have a front-end app with static data inside of the app. You want to move the static data into a mock server, while you're waiting for the back-end team to finish building the API. Performing this step sets you up nicely for using the actual API, once it's done.
In Pizza.jsx, replace the code with the following code:
import { useState, useEffect } from 'react'; import PizzaList from './PizzaList'; const term = "Pizza"; const API_URL = '/pizzas'; const headers = { 'Content-Type': 'application/json', }; function Pizza() { const [data, setData] = useState([]); const [error, setError] = useState(null); useEffect(() => { fetchPizzaData(); }, []); const fetchPizzaData = () => { fetch(API_URL) .then(response => response.json()) .then(data => setData(data)) .catch(error => setError(error)); }; const handleCreate = (item) => { console.log(`add item: ${JSON.stringify(item)}`) fetch(API_URL, { method: 'POST', headers, body: JSON.stringify({name: item.name, description: item.description}), }) .then(response => response.json()) .then(returnedItem => setData([...data, returnedItem])) .catch(error => setError(error)); }; const handleUpdate = (updatedItem) => { console.log(`update item: ${JSON.stringify(updatedItem)}`) fetch(`${API_URL}/${updatedItem.id}`, { method: 'PUT', headers, body: JSON.stringify(updatedItem), }) .then(() => setData(data.map(item => item.id === updatedItem.id ? updatedItem : item))) .catch(error => setError(error)); }; const handleDelete = (id) => { fetch(`${API_URL}/${id}`, { method: 'DELETE', headers, }) .then(() => setData(data.filter(item => item.id !== id))) .catch(error => console.error('Error deleting item:', error)); }; return ( <div> <PizzaList name={term} data={data} error={error} onCreate={handleCreate} onUpdate={handleUpdate} onDelete={handleDelete} /> </div> ); } export default Pizza;
The data is fetched with a call to a mocked API instead of a front-end app's in-memory array. Notice that the URL used is
/pizzas
without the reference to the back-end server. This is because a proxy makes the requests toward the mocked API.Create a file named db.json in the PizzaClient directory. Insert the following content:
{ "pizzas": [ { "id": 1, "name": "Margherita", "description": "Tomato sauce, mozzarella, and basil" }, { "id": 2, "name": "Pepperoni", "description": "Tomato sauce, mozzarella, and pepperoni" }, { "id": 3, "name": "Hawaiian", "description": "Tomato sauce, mozzarella, ham, and pineapple" } ] }
This is a JSON representation of the mocked Pizza data.
Prepare proxy to mock server
In the Visual Studio Code panel, typically found below the editor region, select Ports.
Find the local address for the API on port 5100. Hover over the address and select the copy icon.
Paste this value as the proxy property in the Vite React vite.config.js so the front-end app uses the correct server port.
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], server: { port: 3000, // Client port proxy: { '/pizzas': { target: 'http://localhost:5100', // Mock server port changeOrigin: true, secure: false, ws: true, configure: (proxy, _options) => { proxy.on('error', (err, _req, _res) => { console.log('proxy error', err); }); proxy.on('proxyReq', (proxyReq, req, _res) => { console.log('Sending Request to the Target:', req.method, req.url); }); proxy.on('proxyRes', (proxyRes, req, _res) => { console.log('Received Response from the Target:', proxyRes.statusCode, req.url); }); }, } } } })
Vite reloads the React app to use the new proxy configuration.
Start the mock server
Right-click on the PizzaClient subfolder and select Open in integrated terminal.
Start the mock API with the following command in that new terminal.
npx json-server --watch --port 5100 db.json
Running this code starts the mock server, and output similar to the following appears:
\{^_^}/ hi! Loading db.json Done Resources http://localhost:5100/pizzas Home http://localhost:5100
Use your app in the browser to fetch data from the mock API. Create, update, and delete pizzas to make sure the changes work.
Use Ctrl + C to stop the mock server.
Prepare code to fetch data from .NET API server
Suppose the back-end team has now finished building the server. To use the server, you just need to fetch the code from GitHub and run it and configure CORS as well.
The back-end project is in the PizzaStore subdirectory. Right-click on that subdirectory and select Open in integrated terminal..
Run
dotnet ef database
to apply the migrations to create a database with tables.dotnet ef database update
Note
If the
dotnet ef
can't be found, install it withdotnet tool install -g dotnet-ef
then repeat the previous command.In the file explorer, browse to the PizzaStore directory, and open Program.cs. Replace with the following code to enable CORS (the CORS-related code is highlighted):
using Microsoft.EntityFrameworkCore; using PizzaStore.Data; using Microsoft.OpenApi.Models; using PizzaStore.Models; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("pizzas") ?? "Data Source=pizzas.db"; builder.Services.AddDbContext<PizzaDb>(options => options.UseSqlite(connectionString)); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Pizzas API", Description = "Pizza pizza", Version = "v1" }); }); // 1) define a unique string string MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; // 2) define allowed domains, in this case "http://example.com" and "*" = all // domains, for testing purposes only. builder.Services.AddCors(options => { options.AddPolicy(name: MyAllowSpecificOrigins, builder => { builder.WithOrigins( "http://example.com", "*"); }); }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Pizzas API V1"); }); } // 3) use the capability app.UseCors(MyAllowSpecificOrigins); app.MapGet("/", () => "Hello World!"); app.MapGet("/pizzas", async(PizzaDb db) => await db.Pizzas.ToListAsync()); app.MapPost("/pizzas", async(PizzaDb db, Pizza pizza) => { await db.Pizzas.AddAsync(pizza); await db.SaveChangesAsync(); return Results.Created($"/pizzas/{pizza.Id}", pizza); }); app.MapPut("/pizzas/{id}", async (PizzaDb db, Pizza updatePizza, int id) => { var pizzaItem = await db.Pizzas.FindAsync(id); if (pizzaItem is null) return Results.NotFound(); pizzaItem.Name = updatePizza.Name; pizzaItem.Description = updatePizza.Description; await db.SaveChangesAsync(); return Results.NoContent(); }); app.MapDelete("/pizzas/{id}", async (PizzaDb db, int id) => { var todo = await db.Pizzas.FindAsync(id); if (todo is null) { return Results.NotFound(); } db.Pizzas.Remove(todo); await db.SaveChangesAsync(); return Results.Ok(); }); app.Run();
The changes configure CORS. You'll be able to read and write toward the API, despite the front-end app and API running on different ports.
Prepare proxy to .NET API server URL
In the Visual Studio Code panel, typically found below the editor region, select Ports. The Ports panel appears.
Find the local address for the API on port 5100. Hover over the address and select the copy icon.
Paste this value as the proxy property in the Vite React vite.config.js so the front-end app uses the correct server port.
Start the .NET Core API server
In the integrated terminal for the PizzaStore subfolder, start the .NET Core API in the terminal. The server runs on port 5100.
dotnet run
In the open browser, use the app, it displays one item with the title, Pepperoni.
When you are done testing the app, leave the browser open and let the front-end React app run in the terminal. You can also let the .NET Core API server run in the terminal. You'll use them again in the next exercise.
Congratulations, you've created a full stack application! The React front-end app is reading data from a back-end database via a minimal API.