π¬ Technologies: TypeORM, TypeScript, Node.js, Express.js, Postgres
Creating an API is basically creating a list of endpoints that you want other developers to request whenever they need to interact with your database to Create, Update, Delete or Read information. When using Express.js those endpoints have to be added into your API using the router.get (or post, put or delete) function. For example:
1router.get("/users", getUser)
The line above, specifies a new endpoint that other devs will be able to call by requesting GET /users
.
After declaring your route, you also have to declare your function that will handle that request (in this case getUser
)
The two main files you care about are ./src/<private|public>_routes.ts
and ./src/actions.ts
and you will have to always modify both every time you create a new endpoint.
First you have to think about your endpoint security, who will be using this endpoint? Any public user or only the logged in users?
There are two files and you should update one or the other for each endpoint you create:
public_routes.ts
is for API URLs that are going to be used by anyone, no security whatsoever, for example: Everyone can sign up, everyone can try to login, etc.private_routes.ts
these URLs will be only for logged in users, for example: Get my list of favorites, or get my information, etc.Open your chosen ./src/<private|public>_routes.ts
file and add a new route to the list of endpoints, for example, if we want to build an endpoint to retrieve a single user information by a given ID, for example: GET /user/2
1import { Router } from 'express'; 2import { safe } from './utils'; 3import { getUser } from './actions'; 4const router = Router(); 5 6 7router.get('/user/:id', safe(getUser));
π Note: please note that the
safe
function must always be called before your action or the API errors will be silent.
Open the ./src/actions.ts
and add or re-use one of the action functions, for example:
1export const getUser = async (req: Request, res: Response): Promise<Response> =>{ 2 3 const users = await getRepository(Users).findOne(req.params.id); 4 return res.json(users); 5}
Junior developers always assume that everything will be O.K. while Senior developers are prepared for the worst possible scenarios.
You have to assume that the information is coming in a bad format, for example: Emails don't have the domain name inside, phone numbers have letters, etc.
There are three possible types of validations that we recommend doing:
The request payload can be retrieved by doing req.body
, this is an example on how to validate if the request body contains the property first_name
:
1export const getUser = async (req: Request, res:Response): Promise<Response> =>{ 2 3 // validate that first_name exists or throw a new exception if not. 4 if(!req.body.first_name) throw new Exception("Please provide a first_name") 5 6 // the rest of your function code goes here 7 8})
If your endpoint URL is expecting a parameter you can access it by typing req.params
, for example, if the endpoint GET /user/:id
is called with GET /user/23
, we can retrieve the value like this:
1 const user = await getRepository(Users).findOne(req.params.id); 2 if(!user) throw new Exception("User not found", 404)
We are basically querying the database to get the user with that given ID and make sure it exists.
In the following file π₯π₯π₯ you can find more validation examples.
TypeORM has a lot of ways to retrieve information from the database, we are going to show the most used examples here, and you can check this document for more advanced ways to query information.
β οΈ Important: You must always start by declaring a new
repository
for that entity
1const user = repository.create({ 2 id: 1, 3 firstName: "Timber", 4 lastName: "Saw", 5}) // same as const user = new User(); user.firstName = "Timber"; user.lastName = "Saw";
After having the repository object you can start your query, for example:
Find the user with the first name "Bob"
1userRepository.find({ where: { firstName: "Bob", lastName: "Saw" } });
Find the user with the first name "Bob"
1userRepository.find({ where: { firstName: "Bob", lastName: "Saw" } });
Find the user with the first name "Bob"
1userRepository.findOne(1, { 2 where: { firstName: "Bob" } 3})
π₯π₯π₯ Click here for more advanced query examples
TypeORM is one of the most simple-to-use ORM library.
Here is a very simple example on how to create a new user:
1const user = repository.create({ 2 id: 1, 3 firstName: "Timber", 4 lastName: "Saw" 5}); // same as const user = new User(); user.firstName = "Timber"; user.lastName = "Saw";
Assuming you want to delete user with ID=1
1await repository.delete(1);
Assuming you want to update the user with the ID=1 and set his/her name to Rizzrak:
1await repository.update(1, { firstName: "Rizzrak" });
π₯π₯π₯ Here you can find other example of more complex CRUD operations.
You don't have to use migrations in development mode because TypeORM already does that for you, but before moving to production you have to run the following command to create your migrations:
1$ npm run makemigrations
1$ npm run migrate
This will give you an auto-starting PostgreSQL server (it should auto-start every time you open a new Terminal), plus a few utility scripts that you can run in a Terminal or in a .gitpod.yml command:
pg_start: start the PostgreSQL service
pg_stop: stop the PostgreSQL service
pg_ctl status: check if the PostgreSQL service is running
Once the PostgreSQL server is running, you can use the psql CLI as usual:
$ psql -h localhost -d postgres
psql (10.8 (Ubuntu 10.8-0ubuntu0.18.10.1))
Type "help" for help.
postgres=#
In this article you will find the details about how to implement this schema on your Express API