Exercise - Create an API

Completed

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.

  1. 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.

  2. 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

  1. In the Visual Studio Code panel, typically found below the editor region, select Ports.

  2. Find the local address for the API on port 5100. Hover over the address and select the copy icon.

    {alt-text}

  3. 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

  1. Right-click on the PizzaClient subfolder and select Open in integrated terminal.

  2. 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
    
  3. Use your app in the browser to fetch data from the mock API. Create, update, and delete pizzas to make sure the changes work.

  4. 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.

  1. The back-end project is in the PizzaStore subdirectory. Right-click on that subdirectory and select Open in integrated terminal..

  2. 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 with dotnet tool install -g dotnet-ef then repeat the previous command.

  3. 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

  1. In the Visual Studio Code panel, typically found below the editor region, select Ports. The Ports panel appears.

  2. Find the local address for the API on port 5100. Hover over the address and select the copy icon.

    {alt-text}

  3. 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

  1. In the integrated terminal for the PizzaStore subfolder, start the .NET Core API in the terminal. The server runs on port 5100.

    dotnet run
    
  2. In the open browser, use the app, it displays one item with the title, Pepperoni.

  3. 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.