DEV Community

Cover image for Build your CRUD backend in 20min with Eicrud + MongoDB
Antoine Crosetti
Antoine Crosetti

Posted on

Build your CRUD backend in 20min with Eicrud + MongoDB

In this tutorial, I'm going to demonstrate how Eicrud can save you tons of time when building your Node.js backend.

We're going to make a simple blogging platform where users can read, publish and comment on articles.

Prerequisites


Installation

First, let's set up our app by following Eicrud's documentation.

We create a new NestJS application.

 npm i -g @nestjs/cli
 nest new myblog
 cd myblog
Enter fullscreen mode Exit fullscreen mode

Then install Eicrud on top of it.

npm i -g @eicrud/cli
eicrud setup mongo myblog
Enter fullscreen mode Exit fullscreen mode

Finally, we generate a secret key and store it in a .env file at the root of our project.

echo "JWT_SECRET=$(node -e "console.log(require('crypto').randomBytes(256).toString('base64'));")" > .env
Enter fullscreen mode Exit fullscreen mode

Now we already have a well-structured project! You can check that everything is working.

npm run start
Enter fullscreen mode Exit fullscreen mode

Services

The CLI already generated a User service for us in src\services\user. All we need to add to our application is an Article service and a Comment service.

eicrud generate service Article
eicrud generate service Comment
Enter fullscreen mode Exit fullscreen mode

Model definition

Let's edit our entities to our liking.

Update the file src\services\article\article.entity.ts with the following.

import { Entity, PrimaryKey, Property, 
    ManyToOne } from "@mikro-orm/core";
import { IsString, IsOptional } from "class-validator";
import { CrudEntity } from "@eicrud/core/crud";
import { User } from "../user/user.entity";
import { $MaxSize } from "@eicrud/core/validation";


@Entity()
export class Article implements CrudEntity {

    @PrimaryKey({ name: '_id' })
    @IsString()
    @IsOptional()
    id: string;

    @ManyToOne(() => User)
    @IsString()
    author: User | string;

    @Property()
    @IsString()
    title: string;

    @Property()
    @IsString()
    @$MaxSize(1000)
    content: string;

    @Property()
    createdAt: Date;

    @Property()
    updatedAt: Date;

}
Enter fullscreen mode Exit fullscreen mode

And don't forget our Comment entity in src\services\comment\comment.entity.ts.

import { Entity, PrimaryKey, Property, ManyToOne } from "@mikro-orm/core";
import { IsString, IsOptional } from "class-validator";
import { CrudEntity } from "@eicrud/core/crud";
import { Article } from "../article/article.entity";
import { User } from "../user/user.entity";
import { $MaxSize } from "@eicrud/core/validation";


@Entity()
export class Comment implements CrudEntity {

    @PrimaryKey({ name: '_id' })
    @IsString()
    @IsOptional()
    id: string;

    @ManyToOne(() => Article)
    @IsString()
    article: Article | string;

    @ManyToOne(() => User)
    @IsString()
    author: User | string;

    @Property()
    @IsString()
    @$MaxSize(200)
    content: string;

    @Property()
    createdAt: Date;

    @Property()
    updatedAt: Date;

}
Enter fullscreen mode Exit fullscreen mode

Access rules

Now that we have our model, let's update the access rules in src\services\article\article.security.ts. We want everyone to read our articles, but only their author should be able to modify them.

guest: {
    async defineCRUDAbility(can, cannot, ctx) {
        // guests can read all articles
        can('read', article)
    }
},
user: {
    async defineCRUDAbility(can, cannot, ctx) {
        // user can manage their own articles
        can('crud', article, { author: ctx.userId })
    }
}
Enter fullscreen mode Exit fullscreen mode

It's the same idea for comments, except this time we don't want users to modify their comments after creation. Let's change the rules in src\services\comment\comment.security.ts.

guest: {
    async defineCRUDAbility(can, cannot, ctx) {
        // guests can read all comments
        can('read', comment)
    }
},
user: {
    async defineCRUDAbility(can, cannot, ctx) {
        // user create and delete their own comments
        can('cd', comment, {author: ctx.userId})
    }
}
Enter fullscreen mode Exit fullscreen mode

Commands

Eicrud has prebuilt commands for account creation and login. We just need to allow their usage in src\services\user\cmds\create_account\create_account.security.ts.

guest: {
    async defineCMDAbility(can, cannot, ctx) {
        // can create an account with the role 'user'
        can(create_account, user, {role: 'user'})
    }
}
Enter fullscreen mode Exit fullscreen mode

And in src\services\user\cmds\login\login.security.ts.

guest: {
    async defineCMDAbility(can, cannot, ctx) {
        // Define abilities for user
        can(login, user)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now our backend is ready for action! Let's start the server and switch to another terminal.

npm run start
Enter fullscreen mode Exit fullscreen mode

Calling the API

To simulate our frontend we're going to use ts-node, but you can use your favorite framework or tools like esbuild if you prefer.

npm i -g ts-node # make sur to install it globally
Enter fullscreen mode Exit fullscreen mode

Let's install Eicrud's client.

npm i @eicrud/client
Enter fullscreen mode Exit fullscreen mode

Create a file front.ts and start with a helper class to make Eicrud's client dynamic.

import { ClientConfig, CrudClient } from "@eicrud/client";

export class DynamicClient {
    crudClient: CrudClient<any>;

    constructor() {
        const initalConfig: ClientConfig = {
            url: 'http://localhost:3000',
            serviceName: 'user',
        }
        this.crudClient = new CrudClient(initalConfig);
    }

    get(serviceName){
        this.crudClient.config.serviceName = serviceName;
        return this.crudClient;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can create our first account and publish an article.

// ... following in front.ts

import { ICreateAccountDto } from '@eicrud/shared/interfaces';
import { ILoginDto } from '@eicrud/shared/interfaces';
import { Article } from "./src/services/article/article.entity";

async function main() {
    const client = new DynamicClient();

    // Create account and publish article
    const dto: ICreateAccountDto = {
        email: 'new.user@mail.com',
        password: 'p4ssw0rd',
        role: 'user',
    };
    const { userId } = await client.get('user').cmdS('create_account', dto);

    console.log('User created!', userId);

    const loginDto: ILoginDto = {
        email: 'new.user@mail.com',
        password: 'p4ssw0rd',
    };

    await client.get('user').login(loginDto);

    const newArticle: Partial<Article> = {
        title: 'New article',
        content: 'This is a new article',
        author: userId,
    };

    await client.get('article').create(newArticle);

    // Let's check that our article was created
    const userArticles = await client.get('article').find({ author: userId });
    console.log('User articles:', userArticles);
}

main();
Enter fullscreen mode Exit fullscreen mode

And let's run our frontend to test our API.

ts-node front.ts
Enter fullscreen mode Exit fullscreen mode

That's all for the basics!

Take note of all the CRUD operations available to the client. You can use any of them to query the 'article' or 'comment' service.

If you've had any trouble with this tutorial check out the completed project on GitHub.

Going further

Eicrud is in active development and has many features to make your life easier. You can learn all about it in the documentation. If you find any issue don't hesitate to create one and I'll look into it.

Happy coding!

Top comments (0)