DEV Community

Cover image for The fastest & cheapest way to host your React + .NET 7 API.
Artur Kedzior
Artur Kedzior

Posted on

The fastest & cheapest way to host your React + .NET 7 API.

I have always look at the simplest way to host my indie projects that is also cheap.

Typically there are two elements to host:

  • Database
  • Application

If you are build a SaaS and using framework such as React there are actually three elements:

  • Database
  • API
  • Front-end

Going with any of the big cloud providers makes this setup expensive in monthly payments, especially when you are boot strapping indie project now and then.

I found that the fastest & cheapest way to ship things is to use a combination of a cheap VM from Droplet or Hetzner + Firestore with Firebase.

Service Price Function
Droplet $6/m Host React static app & .net API
Hetzner $4.8/m same as above
Firestore Free* NoSQL database
Firebase Free* identity solution

Firebase & Firestore have a generous free tier which is more than enough to spin up your project fast that will hopefully get successful quickly and it can scale easily.

But what do they do?

Firebase - takes care of your authentication part. You setup the service and they provide you with a npm package to easily integrate your react client code. What does it give you: email + password, gmail, facebook, twitter, github, apple logins out of the box. What you end up storing in your database (user related) is Firebase User ID (UID).

Firestore - is a NoSQL database. Great to start with when you are still shaping your data. It's a good candidate when your data is simple as there is no concept of tables but documents which are basically JSON objects. Given the nature of NoSQL there are some great advantages but also some disadvantages of it. I'm not going to go into details of it here.

Above combo is my favourite when I start with a new indie project. I have basic setup ready within 1 hour.

Let me show here I how set this all up.

Let's start with setting up your .NET API:

Program.cs

        services
            .AddAuthentication(cfg =>
            {
                cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.Authority = $"https://securetoken.google.com/{firebaseSettings.ProjectId}";
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = $"https://securetoken.google.com/{firebaseSettings.ProjectId}",
                    ValidateAudience = true,
                    ValidAudience = firebaseSettings.ProjectId,
                    ValidateLifetime = true
                };
            });

        services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
            .RequireAuthenticatedUser()
            .Build());

        var firebaseJson = JsonSerializer.Serialize(firebaseSettings);

        FirebaseApp.Create(new AppOptions
        {
            Credential = GoogleCredential.FromJson(firebaseJson)
        });

// ...

app.UseAuthorization();
app.UseAuthentication();
Enter fullscreen mode Exit fullscreen mode

🔥 That's all you need for your API! 🔥

But wait, what's firebaseSettings ?

Well you need some way to be able to talk to Firebase.

You need to use this
https://www.nuget.org/packages/FirebaseAdmin

That is letting your API to talk to Firebase and add claims to the token issued by Firebase. For example you might want to store thing like user role.

How this can be set up?

When you get to register with Firebase you will have a section where you can download Service Account json file. Just copy values from it to this class. Alternatively you can use appsettings.json for it.

public class FirebaseSettings
{
    public static FirebaseSettings CreateDevelopment() => new
    (
        "projectId",
        "653464563435645",
        "-----BEGIN PRIVATE KEY-----\privateKey\n-----END PRIVATE KEY-----\n",
        "aaaaaaaaaaaaaa",
        "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-9heco%40GP-72daa.iam.gserviceaccount.com",
        "firebase-adminsdk-yourspecificid.iam.gserviceaccount.com"
    );


    public FirebaseSettings(string projectId, string privateKeyId, string privateKey, string clientId, string clientX509CertUrl, string clientEmail)
    {
        ProjectId = projectId;
        PrivateKeyId = privateKeyId;
        PrivateKey = privateKey;
        ClientId = clientId;
        ClientX509CertUrl = clientX509CertUrl;
        ClientEmail = clientEmail;
    }

    [JsonPropertyName("project_id")]
    public string ProjectId { get; init; }

    [JsonPropertyName("private_key_id")]
    public string PrivateKeyId { get; init; }

    [JsonPropertyName("private_key")]
    public string PrivateKey { get; init; }

    [JsonPropertyName("client_id")]
    public string ClientId { get; init; }

    [JsonPropertyName("client_x509_cert_url")]
    public string ClientX509CertUrl { get; init; }

    [JsonPropertyName("client_email")]
    public string ClientEmail { get; init; }

    [JsonPropertyName("type")]
    public string Type => "service_account";

    [JsonPropertyName("auth_uri")]
    public string AuthUri => "https://accounts.google.com/o/oauth2/auth";

    [JsonPropertyName("token_uri")]
    public string TokenUri => "https://oauth2.googleapis.com/token";

    [JsonPropertyName("auth_provider_x509_cert_url")]
    public string AuthProviderx509CertUrl => "https://www.googleapis.com/oauth2/v1/certs";
}
Enter fullscreen mode Exit fullscreen mode

All you need now is to create an endpoint that is authorized and your Firebase user ID would be within the token. Your Firebase user ID can serve as to map with your internal user ID if your really want to.

[Authorize]
public sealed class GetUser : EndpointBaseAsync
    .WithoutRequest
    .WithActionResult<UserModel>
{

    [HttpGet]
    public override async Task<ActionResult<UserModel>> HandleAsync(CancellationToken cancellationToken = default)
    {
        var firebaseUserId = HttpContext.GetFirebaseId()      
    }
}


public static class HttpContextExtensions
{
    public static string GetFirebaseId(this HttpContext context)
        => context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? throw new Exception("Missing name identifier claim on current user");
}
Enter fullscreen mode Exit fullscreen mode

Now the client part!

You will need firebase npm package .

import firebase from 'firebase/compat/app';
import { firebaseConfig } from '../config';


const login = (email, password) => firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then(() => {
        // After signing with firebase you might want to sign in with your .NET API
        axios.post(`${process.env.REACT_APP_API}/users.signin`).then((response)=> {
          setProfile(response.data);             
        });
      }); 

  const loginWithGoogle = () => {
    const provider = new firebase.auth.GoogleAuthProvider();
    return firebase.auth().signInWithPopup(provider);
  };

  const loginWithFaceBook = () => {
    const provider = new firebase.auth.FacebookAuthProvider();
    return firebase.auth().signInWithPopup(provider);
  };

  const loginWithTwitter = () => {
    const provider = new firebase.auth.TwitterAuthProvider();
    return firebase.auth().signInWithPopup(provider);
  };

  const register = (email, password, firstName, lastName) => 
     firebase
      .auth()
      .createUserWithEmailAndPassword(email, password)
      .then(() => {      
        // After registering with firebase you might want register using your .NET API
        axios.post(`${process.env.REACT_APP_API}/users.signup`, { firstName, lastName, email } )
        .then((response) => {
          setProfile(response.data);
        });         
      });  

  const logout = async () => {
    await firebase.auth().signOut();
};


// That's the config that is obtained from Firebase Console. 

export const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APPID,
  measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID
};

Enter fullscreen mode Exit fullscreen mode

🔥 That's all you need for your client code! 🔥

Well not, you need to build your forms and use those methods. If you want to skip it all along you can also use FirebaseUI which provides you with already made login form.

Drop me any comment if you get stuck or there is something unclear.

Good luck and ....

Made The Code Be with You!

Top comments (2)

Collapse
 
ahmedshahjr profile image
Ahmed Shah

wait where is the api deployment ?

Collapse
 
kedzior_io profile image
Artur Kedzior

I didn't post it here, as the post focuses on places you can host stuff.

I will try to cover how one can host API and front end on Linux VM in the next post.