You might have made your app ready for users, who can now signup and log in to it. A natural next step is to only expose your users to data and content that is theirs. To make this happen, you’ll need to separate the data in your database so you can filter it based on users and accounts.
In this tutorial, I will show this in practice and how you can use Nblocks and its “AuthContext” data to fetch user information and perform basic database filtering to return the correct application data to the logged-in user.
This tutorial will cover a complete use case where we will show how this is done both on the backend and frontend.
We have prepared a frontend and backend project that you can clone from Github and follow the tutorial from start to end.
An auth context available everywhere.
We’ve deployed products based on Nblocks for years. This helps us offload the recurring need to figure out a kick-ass user management model. It’s just there from the start. This “block” should cover a whole use case. In addition to making sure a user can log in, Nblocks covers the needs to connect the users to your application data. So you can show different content depending on who’s logged in.
This shouldn’t sound new to you since I’m talking about a requirement and a need from every SaaS application out there. Think of a Todo app that should show your todos and not anyone else’s or an emailing app that loads your emails and conversations after logging in.
With Nblocks, every logged-in user will provide you with resolved auth context data which you can use to filter your own application data and tailor the user experience. The AuthContext contains the user’s id, the role, granted privileges, tenant id, tenant plan (The payment plan), etc., and is available both in the frontend and backend.
We can use the AuthContext when we store data in the database on behalf of the logged-in user. We can add the user id and tenant id to the data models to filter when we query the data.
Using the AuthContext in the frontend and backend
We’re going to build a Todo app using Nblocks. The app will allow users to log in, create and see their own todos. This will be solved by developing both a frontend app and a backend API.
We’re going to showcase Nblocks on a MNRN stack. This is quite similar to MERN, but a flavour we like to use in Nebulr and the initials stand for MongoDB, NestJS, React and Nodejs. Nblocks provides ready-made plugins for both NestJS and React, which we’ll be using. To keep this tutorial short and simple, we’ll focus on the interesting parts and leave framework-specific configurations out. We’ll be coding in Typescript, but it should be fairly easy to convert to Javascript.
Using the AuthContext in the Backend
First off, we’re going to focus on the backend part where we’ll create a simple data model for our Todo app, and use it in a NestJS service and controller. We will then use this controller from the frontend to push and query data.
You can checkout the branch tutorial-app-filters-start of the backend project and code along yourself or checkout tutorial-app-filters-end to get all code changes.
git checkout tutorial-app-filters-start
Step 1. Add your API key to the project
If you haven’t already, you need to assign your project a Nblocks api key.
Create a new file and name it main.env and place it into the project path nblocks/config/main.env. An example file can be seen here.
Step 2. Building a model with user and tenant data
Let’s create a MongoDB document model called Todo.
- Create a file in the path src/todo.model.ts
- Copy the below code content into the file and save it.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document } from 'mongoose'; export type TodoDocument = Todo & Document; @Schema({ timestamps: true}) export class Todo { @Prop({ required: true }) text: string; @Prop({ required: true, default: false }) done: boolean; @Prop({ required: true, immutable: true }) tenant: string @Prop({ required: true, immutable: true }) user: string } export const TodoSchema = SchemaFactory.createForClass(Todo);
You’ll see in this document class that we have two fields, “text” and “done”, for the application data and two additional fields ”, tenant” and “user”, that will define which account and user this todo belongs to. The next step is to create a service and use the AuthContext to populate the user and tenant fields with id:s.
Step 3. Using the AuthContext
Next file will be a service in which we’ll inject the Todo document model into our Todo service where we can create new and query existing Todos. We’re also injecting the NebulrAuthService, which will serve us with the AuthContext data of the current user by using the method getCurrentAuthContext.
- Create a file in path src/todo.service.ts.
- Paste in the code below.
import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { Todo, TodoDocument } from './todo.model'; import { NebulrAuthService } from '@nebulr-group/nblocks-nestjs' @Injectable() export class TodoService { constructor( @InjectModel(Todo.name) private todoModel: Model<TodoDocument>, private readonly authService: NebulrAuthService ) { } /** * Creates a new todo */ async create( text: string ): Promise<TodoDocument> { const authContext = this.authService.getCurrentAuthContext(); const todo = await new this.todoModel({ text, tenant: authContext.tenantId, user: authContext.userId, }).save(); return todo; } /** * Lists all todos for current tenant */ async list(): Promise<TodoDocument[]> { const authContext = this.authService.getCurrentAuthContext(); const todos = await this.todoModel.find({tenant: authContext.tenantId}).exec(); return todos; } /** * Updates an existing Todo */ async update(args: {id: string, text: string, done: boolean}): Promise<TodoDocument> { const authContext = this.authService.getCurrentAuthContext(); const {id, text, done} = args; const todo = await this.todoModel.findOne({ id, tenant: authContext.tenantId }).exec(); todo.text = text; todo.done = done; await todo.save(); return todo; } }
The code shows how we get the auth context and use it when creating, updating and fetching todos.
Step 4. Expose HTTP endpoints in a controller
Let’s finish off the backend with a Todo controller, which will expose the Rest API endpoints to the frontend.
- Create a file in path src/todo.controller.ts.
- Paste in the code below.
import { Controller, Get, Put, Body, Post, } from '@nestjs/common'; import { TodoService } from './todo.service'; import { TodoDocument } from './todo.model'; @Controller('todo') export class TodoController { constructor( private readonly todoService: TodoService ) { } @Get('') async list(): Promise<TodoDto[]> { const todos = await this.todoService.list(); return todos.map(todo => new TodoDto(todo)); } @Put('') async update( @Body() body: TodoDto, ): Promise<TodoDto> { const todo = await this.todoService.update(body); return new TodoDto(todo); } @Post('') async create( @Body() body: {text: string}, ): Promise<TodoDto> { const todo = await this.todoService.create(body.text); return new TodoDto(todo); } } class TodoDto { id: string; text: string; done: boolean; constructor(todo: TodoDocument) { this.id = todo.id; this.text = todo.text; this.done = todo.done; } }
Step 5. Start the backend server
From terminal, navigate to project root and run
npm run start:dev
to start the server.
Perfect, now everything is in place on the backend side. It’s time to turn our heads to the frontend.
Build a simple frontend component to see the result in action
You can checkout the branch tutorial-app-filters-start of the frontend project and code along yourself or checkout tutorial-app-filters-end to get all code changes.
Like this:
git checkout tutorial-app-filters-start
We will now create a simple React component that lists all your Todos on page load and allows for new Todos to be created. Let’s call it TodoComponent.
In this component, we declare two methods, listTodos and createTodo. In both these methods, we call the backend to either return or save data. When calling the backend we must attach the user’s authentication tokens to the call so that the backend can resolve these, perform authorization and make the AuthContext object available in the backend.
Instead of doing this manually for each call, we can use a helper from the Nblocks plugin. This is an auth-loaded HTTP client, which you can obtain using the useSecureContext() react hook. This client will automatically ensure that the data is correctly added to the HTTP headers for all calls made to the backend.
- Create a file in path src/TodoComponent.tsx.
- Paste in the code below.
import { useSecureContext } from '@nebulr-group/nblocks-react'; import React, { useEffect, useState } from 'react'; const Todos = () => { const { authHttpClient } = useSecureContext(); const [todos, setTodos] = useState<TodoDto[]>([]); const [text, setText] = useState(""); useEffect(() => { listTodos(); }, []) const listTodos = async () => { const response = await authHttpClient.httpClient.get<TodoDto[]>("http://localhost:3000/todo"); setTodos(response.data); } const createTodo = async () => { const response = await authHttpClient.httpClient.post<TodoDto>("http://localhost:3000/todo", {text}); setTodos([...todos, response.data]); setText(""); } return ( <div> <div> {todos.map(todo => ( <div key={todo.id} className={todo.done ? 'checked-icon' : ''}> <p>{todo.text}</p> </div> ))} </div> <div> <textarea value={text} onChange={(e) => setText(e.target.value)}></textarea> <button onClick={() => createTodo()}></button> </div> </div> ); } type TodoDto = { id: string; text: string; done: boolean; } export { Todos };
- Open up App.tsx, import the TodoComponent and put it as a child to the NblocksProvider.
<NblocksProvider config={{devMode: true}}> <Todos /> </NblocksProvider>
- Start the frontend by running npm start.
Testing the result
Start both the frontend and backend project and navigate to the app with your browser. After login, you’ll arrive at the Todo component. Try creating a new Todo and see what happens.
For every todo you’re creating, the list keeps increasing.
These are your own Todos, connected to your user.
To verify this you can create a second user, login with it and try out the same thing.
Also have a look in your database if you want to see what has been saved.
Here’s a screenshot of the database content where my todos have been saved and linked to my user.
Bringing it to your own app
As you notice we follow a simple pattern where you need to connect the Nblocks-specific users and tenant Id:s to your application-specific data.
You can use the same approach as used in this tutorial in your own app regardless if you only
The AuthContext we provide is usable both on the backend and frontend.
If you use other frameworks or languages than we show in our examples, the AuthContext can be fetched via REST calls that you can find in this documentation
We love to hear how you use Nblocks, and if you have any thoughts, ideas or feedback, please reach out and chat with us!
Who am I?
My name is Oscar and I’m the CTO of Nebulr, the company behind Nblocks. I’ve dreamt about building stuff since I was a kid and when I started programming as a teenager I got stuck.
As a programmer by heart, with 15 years in the tech industry and experience from more than 50+ projects under my belt I’ve seen and learnt a lot about scoping and solving problems in a pragmatic way.
I enjoy identifying real-world problems as much as exploring new technologies and I thrive when finding a connection between these two.
I’m a firm believer that you never know where you’re going to end up, before you’ve put it in the hands of your users. This is what we practice at Nebulr.