Express Controllers
It's time to create our actual API endpoints to create, mark as complete, and delete our todo items. We will create a new controller to handle these actions.
Core Express Controllers
First, we need to install the universal-core-express-controllers package:
npm install @universal-packages/core-express-controllersLet's initialize the universal-core-express-controllers package by running the following command:
ucore initialize express-controllersucore initialize express-controllers --typescriptNow we have the common express controllers directory structure in our project, as well as our config file at .src/config/express-controllers-app.yaml.
In the previous chapter, when we initialized TypeORM, we received the config file typeorm-module.yaml. The module suffix is due to the fact that core-typeorm provides a CoreModule that can be used across applications. In contrast, core-express-controllers provides a CoreApp that runs an HTTP server and makes our controller's endpoints available. That's why its configuration files reflect this difference in purpose.
Configuring CORS
Let's configure CORS in our express-controllers-app.yaml file.
cors: trueYou can also configure the allowed origins, methods, and headers.
cors:
origin: "*"
methods: "GET,HEAD,PUT,PATCH,POST,DELETE"
headers: "Origin, X-Requested-With, Content-Type, Accept"And install the cors package.
npm install corsThis is necessary to allow our frontend to make requests to our backend since they are running on different ports, and that counts as a cross-origin request.
TodoItems Controller
Let's create our first controller for our TodoItems. Create a new file in the src/controllers directory called TodoItems.controller.js TodoItems.controller.ts .controller suffix is a convention universal-core-express-controllers uses to identify controllers.
This is how our TodoItemsController should look:
import {
BaseController,
Controller,
Get,
} from "@universal-packages/express-controllers";
import { TodoItem } from "../entity/TodoItem";
@Controller("/todo-items")
export default class TodoItemsController extends BaseController {
@Get()
async index() {
const todoItems = await TodoItem.find();
return this.status("OK").json({ todoItems });
}
}import {
BaseController,
Controller,
Get,
} from "@universal-packages/express-controllers";
import { TodoItem } from "../entity/TodoItem";
@Controller("/todo-items")
export default class TodoItemsController extends BaseController {
@Get()
public async index(): Promise<this> {
const todoItems = await TodoItem.find();
return this.status("OK").json({ todoItems });
}
}§
We have created a new controller that will handle the GET request to the /todo-items endpoint. This controller will return all the todo items in the database. You can test this by visiting http://localhost:3000/todo-items in your browser.
If you created some todo items in the previous chapter, you should see them in the response.
{
"todoListItems": [
{
"id": "1",
"content": "Buy milk",
"done": false,
"createdAt": "2024-07-17T02:03:41.658Z",
"updatedAt": "2024-07-17T02:03:41.658Z"
}
]
}Create Todo Item
Let's add a new method to our TodoItemsController to create a new todo item:
@Post({ bodyParser: "json" })
async create() {
const todoItem = new TodoItem();
todoItem.content = this.request.body.content;
todoItem.done = false;
await todoItem.save();
return this.status("CREATED").json({ todoItem });
} @Post({ bodyParser: "json" })
public async create(): Promise<this> {
const todoItem = new TodoItem();
todoItem.content = this.request.body.content;
todoItem.done = false;
await todoItem.save();
return this.status("CREATED").json({ todoItem });
}Here we are using the @Post decorator to handle the POST request to the /todo-items endpoint. We expect the request body to have a content property, which we will use to create the new todo item.
Update Todo Item
Let's add a new method to our TodoItemsController to update a todo item:
@Put("/:id", { bodyParser: "json" })
async update() {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
if (this.request.body.content)
todoItem.content = this.request.body.content;
if (this.request.body.done) todoItem.done = this.request.body.done;
await todoItem.save();
return this.status("OK").json({ todoItem });
} catch (error) {
return this.status("NOT_FOUND");
}
} @Put("/:id", { bodyParser: "json" })
async update() {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
if (this.request.body.content)
todoItem.content = this.request.body.content;
if (this.request.body.done !== undefined) todoItem.done = this.request.body.done;
await todoItem.save();
return this.status("OK").json({ todoItem });
} catch (error) {
return this.status("NOT_FOUND");
}
}Here we are using the @Put decorator to handle the PUT request to the /todo-items/:id endpoint. We expect the request body to have a content and/or done property, which we will use to update the todo item.
Delete Todo Item
Lets add a new method to our TodoItemsController to delete a todo item:
@Delete("/:id")
async delete() {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
await todoItem.remove();
return this.status("NO_CONTENT");
} catch (error) {
return this.status("NOT_FOUND");
}
} @Delete("/:id")
async delete() {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
await todoItem.remove();
return this.status("NO_CONTENT");
} catch (error) {
return this.status("NOT_FOUND");
}
}Here we are using the @Delete decorator to handle the DELETE request to the /todo-items/:id endpoint. We expect the request to have an id parameter, which we will use to delete the todo item.
Final controller
This is how our TodoItemsController should look like:
import {
BaseController,
Controller,
Get,
Post,
Put,
Delete,
} from "@universal-packages/express-controllers";
import { TodoItem } from "../entity/TodoItem";
@Controller("/todo-items")
export default class TodoItemsController extends BaseController {
@Get()
async index() {
const todoItems = await TodoItem.find();
return this.status("OK").json({ todoItems });
}
@Post({ bodyParser: "json" })
async create() {
const todoItem = new TodoItem();
todoItem.content = this.request.body.content;
todoItem.done = false;
await todoItem.save();
return this.status("CREATED").json({ todoItem });
}
@Put("/:id", { bodyParser: "json" })
async update() {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
if (this.request.body.content)
todoItem.content = this.request.body.content;
if (this.request.body.done !== undefined) todoItem.done = this.request.body.done;
await todoItem.save();
return this.status("OK").json({ todoItem });
} catch (error) {
return this.status("NOT_FOUND");
}
}
@Delete("/:id")
async delete() {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
await todoItem.remove();
return this.status("NO_CONTENT");
} catch (error) {
return this.status("NOT_FOUND");
}
}
}import {
BaseController,
Controller,
Get,
Post,
Put,
Delete,
} from "@universal-packages/express-controllers";
import { TodoItem } from "../entity/TodoItem";
@Controller("/todo-items")
export default class TodoItemsController extends BaseController {
@Get()
public async index(): Promise<this> {
const todoItems = await TodoItem.find();
return this.status("OK").json({ todoItems });
}
@Post({ bodyParser: "json" })
public async create(): Promise<this> {
const todoItem = new TodoItem();
todoItem.content = this.request.body.content;
todoItem.done = false;
await todoItem.save();
return this.status("CREATED").json({ todoItem });
}
@Put("/:id", { bodyParser: "json" })
public async update(): Promise<this> {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
if (this.request.body.content)
todoItem.content = this.request.body.content;
if (this.request.body.done !== undefined)
todoItem.done = this.request.body.done;
await todoItem.save();
return this.status("OK").json({ todoItem });
} catch (error) {
return this.status("NOT_FOUND");
}
}
@Delete("/:id")
public async delete(): Promise<this> {
try {
const todoItem = await TodoItem.findOneOrFail({
where: { id: this.request.params.id },
});
await todoItem.remove();
return this.status("NO_CONTENT");
} catch (error) {
return this.status("NOT_FOUND");
}
}
}App watcher
For development, universal-core will watch for file changes and reload the application. After updating the controller, feel free to just wait for it to reload.
If you prefer not to have this feature enabled, take a look at the core.yaml file in the root of your project.
NPM start
This will be our main application, so it would be nice to have a script to start it. Let's add a new script to our package.json:
{
"scripts": {
"start": "ucore run express-controllers"
}
}Summary
Now we have the endpoints to create, update, delete, and list todo items. In the next chapter, we will start creating the frontend to interact with these endpoints.