Search code examples
reactjs.nettypescriptminimal-apis

React with .NET 8.0 + Minimal api, frontend sends http requests to its own port instead of backend port


Previously I was working with react projects on .NET 3.1, and I just tried to create a fresh project on .NET 8.0 and encountered a lot of troubles and misunderstandings. I created a new controller and now trying to access it method, but I get the error 404 Not Found. I found out that frontend is trying to send requests to its own port instead of backend port. When I press "Run", my project starts in two windows: one is google chrome that represents backend, I guess, and one in edge where frontend is. So, in the google chrome window the link is http://localhost:5239/ and in edge the link is https://localhost:5173/ which is also uncommon to me.

This is my controller:

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    private readonly UserRepository _repo;

    public UsersController(UserRepository repo)
    {
        _repo = repo;
    }

    [HttpPost("[action]")]
    public async Task<IActionResult> RegisterUser(User user)
    {
        try
        {
            var existingUser = await _repo.GetUserByUsernameAsync(user.Login);
            if (existingUser != null)
            {
                return Conflict("This login already exists.");
            }

            // Hash the password using BCrypt
            string hashedPassword = BCrypt.Net.BCrypt.HashPassword(user.Password);

            // Replace the plaintext password with the hashed password
            user.Password = hashedPassword;

            var result = await _repo.CreateUserAsync(user, user.Password);

            return Ok(result);
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }

    [HttpPost("[action]")]
    public async Task<IActionResult> GetUserByUserName(string username)
    {
        try
        {
            return Ok(await _repo.GetUserByUsernameAsync(username));
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }
}

This are my Program.cs file contents:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

// Configure the HTTP request pipeline.

app.UseAuthorization();
app.UseHttpsRedirection();
app.UseRouting();

app.MapControllers();

app.MapFallbackToFile("/index.html");

app.Run();

This is my react component:

import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import './register.scss';

const Register: React.FC = () => {
    const [username, setUsername] = useState('');
    const [fio, setFio] = useState('');
    const [password, setPassword] = useState('');
    const navigate = useNavigate();

    const handleRegister = async () => {
        try {
          const response = await fetch('api/users/RegisterUser', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({ login: username, name: fio, password: password }),
          });
    
          if (response.ok) {
            console.log('User registered successfully');
          } else {
            console.error('Registering user failed');
          }
        } catch (error) {
          console.error('Error on registering user:', error);
        }
      };

    return (
        <div className="register-container">
            <h2>Регистрация</h2>
            <div className='register-content'>
                <div>
                    <label htmlFor="username">Login:</label>
                    <input
                        type="text"
                        id="username"
                        value={username}
                        onChange={(e) => setUsername(e.target.value)}
                        required
                    />
                </div>
                <div>
                    <label htmlFor="fio">Name:</label>
                    <input
                        type="text"
                        id="fio"
                        value={fio}
                        onChange={(e) => setFio(e.target.value)}
                        required
                    />
                </div>
                <div>
                    <label htmlFor="password">Password:</label>
                    <input
                        type="password"
                        id="password"
                        value={password}
                        onChange={(e) => setPassword(e.target.value)}
                        required
                    />
                </div>
                <button onClick={handleRegister}>Register</button>
            </div>
            <p>
                Already have an account? <Link to="/login">Log in</Link>
            </p>
        </div>
    );
};

export default Register;

These are my response headers:

HTTP/1.1 404 Not Found
Access-Control-Allow-Origin: *
Date: Wed, 15 May 2024 08:34:36 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 0

These are my request headers:

POST /api/users/RegisterUser HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: ru,en;q=0.9,en-GB;q=0.8,en-US;q=0.7
Connection: keep-alive
Content-Length: 45
Content-Type: application/json
Host: localhost:5173
Origin: https://localhost:5173
Referer: https://localhost:5173/register
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
sec-ch-ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

Please, tell me, what am I doing wrong or how to properly configure my app, I feel completely lost and destroyed by the modern .NET. Also please ask if you need any additional info/code.


Solution

  • In your react code in the function handleRegister you have

    const response = await fetch('api/users/RegisterUser', {
          method: 'POST',
          headers: {
          'Content-Type': 'application/json',
    },
    

    The fetch without a base url will be called on the current url of the page, so in this case it's just appending 'api/users/RegisterUser' to your frontend url. You must set the backend url in the fetch target:

    fetch('http://localhost:5239/api/users/RegisterUser'...

    I suggest to have it stored somehow as env or global variable and set in every backend endpoint call. (but that's not my field i cannot say what's the best practice).

    Also just for you to know this is not a minimal api aproach (as stated in the title), but it's a classic controller.