diff --git a/package.json b/package.json
index 4ba4e23546bbd69c1acc379b3b5dc215221e0e95..75519d1a185803bc5f195ae48fccf8593021cded 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,8 @@
     "@nestjs/core": "^9.0.0",
     "@nestjs/mongoose": "^9.2.1",
     "@nestjs/platform-express": "^9.0.0",
+    "class-transformer": "^0.5.1",
+    "class-validator": "^0.13.2",
     "mongoose": "^6.7.2",
     "reflect-metadata": "^0.1.13",
     "rimraf": "^3.0.2",
diff --git a/src/app.module.ts b/src/app.module.ts
index 92af588a597dd86698840699a99e1371eaa77f43..2c1d559b9754cb0c4a18024daacc5881f1fc13c0 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -1,10 +1,9 @@
 import { Module } from '@nestjs/common';
 import { MongooseModule } from '@nestjs/mongoose';
 import { mongodb } from './config';
+import { GroupsModule } from './groups/groups.module';
 
 @Module({
-  imports: [MongooseModule.forRoot(mongodb.uri)],
-  controllers: [],
-  providers: [],
+  imports: [GroupsModule, MongooseModule.forRoot(mongodb.uri)],
 })
 export class AppModule {}
diff --git a/src/groups/dao/groups.dao.ts b/src/groups/dao/groups.dao.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e053bb8bdf2de3f9d8a9d87e8fef28f8646aac1
--- /dev/null
+++ b/src/groups/dao/groups.dao.ts
@@ -0,0 +1,38 @@
+import { Injectable } from '@nestjs/common';
+import { InjectModel } from '@nestjs/mongoose';
+import { Model } from 'mongoose';
+import { from, map, Observable } from 'rxjs';
+import { CreateGroupDto } from '../dto/create-group.dto';
+import { UpdateGroupDto } from '../dto/update-group.dto';
+import { Group } from '../schemas/group.schema';
+
+@Injectable()
+export class GroupsDao {
+  constructor(
+    @InjectModel(Group.name)
+    private readonly _groupModel: Model<Group>,
+  ) {}
+
+  find = (): Observable<Group[]> =>
+    from(this._groupModel.find({})).pipe(map((groups) => [].concat(groups)));
+
+  findById = (id: string): Observable<Group | void> =>
+    from(this._groupModel.findById(id));
+
+  save = (group: CreateGroupDto): Observable<Group> =>
+    from(new this._groupModel(group).save());
+
+  findByIdAndUpdate = (
+    id: string,
+    group: UpdateGroupDto,
+  ): Observable<Group | void> =>
+    from(
+      this._groupModel.findByIdAndUpdate(id, group, {
+        new: true,
+        runValidators: true,
+      }),
+    );
+
+  findByIdAndRemove = (id: string): Observable<Group | void> =>
+    from(this._groupModel.findByIdAndRemove(id));
+}
diff --git a/src/groups/dto/create-group.dto.ts b/src/groups/dto/create-group.dto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44a55e1f531a6c8ac1f90d1ef932e5030105473d
--- /dev/null
+++ b/src/groups/dto/create-group.dto.ts
@@ -0,0 +1,14 @@
+import { IsBoolean, IsString, IsNotEmpty, IsMongoId } from 'class-validator';
+
+export class CreateGroupDto {
+  @IsString()
+  @IsNotEmpty()
+  id: string;
+
+  @IsBoolean()
+  final: boolean;
+
+  @IsMongoId()
+  @IsNotEmpty()
+  parent: any;
+}
diff --git a/src/groups/dto/update-group.dto.ts b/src/groups/dto/update-group.dto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..954e8525bd1ae8bfec355690699cc1c47617395d
--- /dev/null
+++ b/src/groups/dto/update-group.dto.ts
@@ -0,0 +1,14 @@
+import { IsBoolean, IsString, IsNotEmpty, IsMongoId } from 'class-validator';
+
+export class UpdateGroupDto {
+  @IsString()
+  @IsNotEmpty()
+  id: string;
+
+  @IsBoolean()
+  final: boolean;
+
+  @IsMongoId()
+  @IsNotEmpty()
+  parent: string;
+}
diff --git a/src/groups/entities/group.entity.ts b/src/groups/entities/group.entity.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b9182cd90ebb2eaec1c5230088d9777ef7b2d2bb
--- /dev/null
+++ b/src/groups/entities/group.entity.ts
@@ -0,0 +1,15 @@
+import { Group } from '../schemas/group.schema';
+
+export class GroupEntity {
+  _id: string;
+  id: string;
+  final: boolean;
+  responsibles: string[];
+  secretaries: string[];
+  students: string[];
+  parent: string;
+
+  constructor(partial: Partial<Group>) {
+    Object.assign(this, partial);
+  }
+}
diff --git a/src/groups/groups.controller.ts b/src/groups/groups.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..191fe59226a449282db80421823dd90de5f23100
--- /dev/null
+++ b/src/groups/groups.controller.ts
@@ -0,0 +1,47 @@
+import {
+  Controller,
+  Get,
+  Post,
+  Put,
+  Delete,
+  Param,
+  Body,
+} from '@nestjs/common';
+import { Observable } from 'rxjs';
+import { CreateGroupDto } from './dto/create-group.dto';
+import { UpdateGroupDto } from './dto/update-group.dto';
+import { GroupEntity } from './entities/group.entity';
+import { GroupsService } from './groups.service';
+
+@Controller('groups')
+export class GroupsController {
+  constructor(private readonly _groupsService: GroupsService) {}
+
+  @Get()
+  findAll(): Observable<GroupEntity[] | void> {
+    return this._groupsService.findAll();
+  }
+
+  @Get(':id')
+  findOne(@Param() params: { id: string }): Observable<GroupEntity> {
+    return this._groupsService.findOne(params.id);
+  }
+
+  @Post()
+  create(@Body() createGroupDto: CreateGroupDto): Observable<GroupEntity> {
+    return this._groupsService.create(createGroupDto);
+  }
+
+  @Put(':id')
+  update(
+    @Param() params: { id: string },
+    @Body() updateGroupDto: UpdateGroupDto,
+  ): Observable<GroupEntity> {
+    return this._groupsService.update(params.id, updateGroupDto);
+  }
+
+  @Delete(':id')
+  delete(@Param() params: { id: string }): Observable<void> {
+    return this._groupsService.delete(params.id);
+  }
+}
diff --git a/src/groups/groups.module.ts b/src/groups/groups.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3320cf0c09744da9567ab8e266c5d7460f034792
--- /dev/null
+++ b/src/groups/groups.module.ts
@@ -0,0 +1,15 @@
+import { Module } from '@nestjs/common';
+import { MongooseModule } from '@nestjs/mongoose';
+import { GroupsDao } from './dao/groups.dao';
+import { GroupsController } from './groups.controller';
+import { GroupsService } from './groups.service';
+import { Group, GroupSchema } from './schemas/group.schema';
+
+@Module({
+  imports: [
+    MongooseModule.forFeature([{ name: Group.name, schema: GroupSchema }]),
+  ],
+  controllers: [GroupsController],
+  providers: [GroupsService, GroupsDao],
+})
+export class GroupsModule {}
diff --git a/src/groups/groups.service.ts b/src/groups/groups.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..798420c06d7c5a13b5335d4f8b965e2534689157
--- /dev/null
+++ b/src/groups/groups.service.ts
@@ -0,0 +1,109 @@
+import {
+  Injectable,
+  UnprocessableEntityException,
+  NotFoundException,
+  ConflictException,
+} from '@nestjs/common';
+import {
+  Observable,
+  of,
+  filter,
+  map,
+  mergeMap,
+  defaultIfEmpty,
+  catchError,
+  throwError,
+} from 'rxjs';
+import { GroupsDao } from './dao/groups.dao';
+import { CreateGroupDto } from './dto/create-group.dto';
+import { UpdateGroupDto } from './dto/update-group.dto';
+import { GroupEntity } from './entities/group.entity';
+
+@Injectable()
+export class GroupsService {
+  constructor(private readonly _groupsDao: GroupsDao) {}
+
+  findAll = (): Observable<GroupEntity[] | void> =>
+    this._groupsDao.find().pipe(
+      filter(Boolean),
+      map((groups) => (groups || []).map((group) => new GroupEntity(group))),
+      defaultIfEmpty(undefined),
+    );
+
+  findOne = (id: string): Observable<GroupEntity> =>
+    this._groupsDao.findById(id).pipe(
+      catchError((e) =>
+        throwError(() => new UnprocessableEntityException(e.message)),
+      ),
+      mergeMap((group) =>
+        !!group
+          ? of(new GroupEntity(group))
+          : throwError(
+              () => new NotFoundException(`Group with id ${id} not found`),
+            ),
+      ),
+    );
+
+  create = (group: CreateGroupDto): Observable<GroupEntity> =>
+    this._prepareNewGroup(group).pipe(
+      mergeMap((newPreparedGroup: CreateGroupDto) =>
+        this._groupsDao.save(newPreparedGroup),
+      ),
+      catchError((e) =>
+        e.code === 11000
+          ? throwError(
+              () =>
+                new ConflictException(
+                  `Group with id ${group.id} already exists`,
+                ),
+            )
+          : throwError(() => new UnprocessableEntityException(e.message)),
+      ),
+      map((groupCreated) => new GroupEntity(groupCreated)),
+    );
+
+  update = (id: string, group: UpdateGroupDto): Observable<GroupEntity> =>
+    this._groupsDao.findByIdAndUpdate(id, group).pipe(
+      catchError((e) =>
+        e.code === 11000
+          ? throwError(
+              () =>
+                new ConflictException(
+                  `Group with id ${group.id} already exists`,
+                ),
+            )
+          : throwError(() => new UnprocessableEntityException(e.message)),
+      ),
+      mergeMap((groupUpdated) =>
+        !!groupUpdated
+          ? of(new GroupEntity(groupUpdated))
+          : throwError(
+              () => new NotFoundException(`Group with id '${id}' not found`),
+            ),
+      ),
+    );
+
+  delete = (id: string): Observable<void> =>
+    this._groupsDao.findByIdAndRemove(id).pipe(
+      catchError((e) =>
+        throwError(() => new UnprocessableEntityException(e.message)),
+      ),
+      mergeMap((groupDeleted) =>
+        !!groupDeleted
+          ? of(undefined)
+          : throwError(
+              () => new NotFoundException(`Group with id '${id}' not found`),
+            ),
+      ),
+    );
+
+  private _prepareNewGroup = (
+    group: CreateGroupDto,
+  ): Observable<CreateGroupDto> =>
+    of({
+      ...group,
+      responsibles: [],
+      secretaries: [],
+      students: [],
+    });
+}
diff --git a/src/groups/groups.types.ts b/src/groups/groups.types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ddbd330595cec2678076afcfe88ea02cd5f0e4a4
--- /dev/null
+++ b/src/groups/groups.types.ts
@@ -0,0 +1,10 @@
+export type Group = {
+  _id: any;
+  id: string;
+  final: boolean;
+  responsibles: string[];
+  secretaries: string[];
+  students: string[];
+  subgroups: string[];
+  parent: string;
+};
diff --git a/src/groups/schemas/group.schema.ts b/src/groups/schemas/group.schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c8ee51ecb15cf7834bcc8a66a1f351ae626aa78c
--- /dev/null
+++ b/src/groups/schemas/group.schema.ts
@@ -0,0 +1,67 @@
+import * as mongoose from 'mongoose';
+import { Document } from 'mongoose';
+import { Prop, raw, Schema, SchemaFactory } from '@nestjs/mongoose';
+
+export type GroupDocument = Group & Document;
+
+@Schema({
+  toJSON: {
+    virtuals: true,
+    transform: (doc: any, ret: any) => {
+      delete ret._id;
+    },
+  },
+})
+export class Group {
+  @Prop({
+    type: mongoose.Schema.Types.ObjectId,
+    auto: true,
+  })
+  _id: any;
+
+  @Prop({
+    type: String,
+    required: true,
+    trim: true,
+  })
+  id: string;
+
+  @Prop({
+    type: Boolean,
+    required: true,
+    trim: true,
+  })
+  final: boolean;
+
+  @Prop({
+    type: [mongoose.Schema.Types.ObjectId],
+    required: true,
+    trim: true,
+  })
+  responsibles: string[];
+
+  @Prop({
+    type: [mongoose.Schema.Types.ObjectId],
+    required: true,
+    trim: true,
+  })
+  secretaries: string[];
+
+  @Prop({
+    type: [mongoose.Schema.Types.ObjectId],
+    required: true,
+    trim: true,
+  })
+  students: string[];
+
+  @Prop({
+    type: mongoose.Schema.Types.ObjectId,
+    required: true,
+    trim: true,
+  })
+  parent: string;
+}
+
+export const GroupSchema = SchemaFactory.createForClass(Group);
+
+GroupSchema.index({ id: 1 }, { unique: true });