Crea una API con Typescript, Node, Express y MySQL

Instalaciones y configuraciones

Empezamos ejecutando el siguiente comando:

npm init -y

Esto nos crea un package.json que permite configurar nuestra app y sus dependencias para funcionar.Instalamos las dependencias.

npm i -D typescript nodemon ts-node @types/express @types/morgan types/mysql2
npm i express morgan mysql2

Volviendo al package.json, añadiremos dos scripts:

"build": "tsc",

"dev": "nodemon src/index.ts --exec ts-node"

  • npm run build: compila todo el código y lo transpila a Javascript.
  • npm run dev: ejecuta un live server que se actualizará cada vez que se realize un cambio en el código.

Ahora debemos crear el archivo que nos permitirá configurar nuestro código Typescript:

npx typescript --init

Accedemos al archivo tsconfig.json y hacemos los siguientes cambios:

  • target: "ec6" (Habilita Ecmascript 6)
  • outDir: "./dist" (indica donde se convertirá todo el código TS)

Desarrollo

Para comenzar con nuestra API crearemos en la raíz una carpeta src donde tendremos toda nuestra aplicación. El primer archivo de todos será un index.ts que se encargará de arrancar la app y la própia aplicacion en otro archivo llamado app.ts.

app.ts

Lo primero de todo es importar express para poder construirla, crearemos una clase llamada App que contendrá dentro de su constructor todos los atributos necesarios y sus métodos.

import express, { Application } from "express"
export class App {
private app: Application
constructor(private port?: number | string) {
this.app = express()
this.setting()
}
setting() {
this.app.set("port", this.port || process.env.PORT || 3000)
}
async listen() {
await this.app.listen(this.app.get("port"))
console.log("Server on port ", this.app.get("port"))
}
}
  • this.app() = express(): devuelve un objeto Application que nos permitirá utilizar nuestra aplicación.
  • this.settings(): permite utilizar los ajustes personalizados en los métodos.

En la llamada a settings hemos configurado la variable port para utilizar una variable de entorno o en su defecto asignar el puerto 3000. Por último llamamos al método listen que levanta el servidor y permanece a la escucha de peticiones.

Para que todo se ejecute debemos crear la función main que invocará nuestra App y la ejecutará dentro de index.ts:

import { App } from "./app"
async function main() {
const app = new App()
await app.listen()
}
main()

Middlewares

Añadiremos los siguientes middlewares para poder utilizar morgan y que el servidor acepte las respuestas de tipo Json, éstos métodos formaran parte de la clase App:

import morgan from 'morgan'
middlewares(){
this.app.use(morgan('dev'))
this.app.use(express.json())
}

Rutas

Empezaremos a crear nuestra primera ruta, la raíz "/". Para tener un código limpio creamos una carpeta llama routes donde añadiremos un nuevo archivo para cada tipo de ruta. La primera será index.route.ts.

import { Router } from "express"
const router = Router()
router.route("/").get(indexWelcome)
export default router

Lo que estamos haciendo es crear un elemento Router que gestionara la llamada a la ruta asignada, en este caso la raíz. Una vez se haga una llamada a esa dirección se ejecutará la función indexWelcome. Para la mayor legilibilidad separaremos la lógica de las rutas, para ello crearemos otra carpeta llamada controllers. Dentro de controllers crearemos nuestro primer controlador index.controller.ts que se encargará de responder al cliente que haya realizado una llamada a la raíz de nuestra API:

import { Request, Response } from "express"
export function indexWelcome(req: Request, res: Response): Response {
return res.json("Welcome to my API")
}

En este caso devolveremos un texto en formato json dándo la bienvenida al usuario. Ahora debemos importar la lógica del controlador dentro de la ruta para poder ejectuarla, el resultado de nuestra ruta index.routes.ts quedará de la siguiente forma:

import { Router } from "express"
import { indexWelcome } from "../controllers/index.controller"
const router = Router()
router.route("/").get(indexWelcome)
export default router

Crear la base de datos

Primero de todo debemos instalar MySQL, en este post no vamos a explicar como hacerlo pero es relativamenta fácil (https://www.mysql.com/downloads/). Una vez lo tengamos instalado crearemos una nueva base de datos y una tabla donde persistirán todos los posts.

CREATE DATABASE node_mysql_ts;
CREATE TABLE posts(
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
image_url TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
DESCRIBE post;

Modelo de Post

Para trabajar con Typescript debemos de crear una interface de post para saber que atributos esperaremos. De nuevo creamos otra carpeta llamada interface y dentro de ella el archivo Post.interface.ts, que al igual que en el código SQL describimos lo siguiente:

export interface IPost {
id?: String
title: String
description: String
image_url: String
created_at: Date
}

Conexión con la base de datos

Para interactuar con nuestra base de datos debemos de crear una conexión entre la aplicación y ésta. En el directorio raíz crearemos un nuevo archivo database.ts. En él crearemos una conexión con la siguiente configuración:

import { createPool } from "mysql2/promise"
export async function connect() {
const connection = await createPool({
host: "localhost",
user: "root",
password: "root",
database: "node_mysql_ts",
connectionLimit: 10,
})
return connection
}

El objeto que devolvemos és el encargado de hacer las conexiones.

Rutas a Post

Para manejar todas las rutas relacionadas con los posts creamos un nuevo archivo src/routes/posts.routes.ts. Dentro añadiremos dos rutas:

/posts

  • get: Obtiene todos los posts de la DB
  • post: Crea un nuevo post en la DB

/posts/:postId

  • get: Obtiene el post con la id indicada
  • delete: Elimina el post con la id indicada
  • put: Actualiza el post indicado
import { Router } from "express"
import {
createPost,
getPosts,
getPost,
deletePost,
updatePost,
} from "../controllers/posts.controller"
const router = Router()
router.route("/posts").get(getPosts).post(createPost)
router.route("/posts/:postId").get(getPost).delete(deletePost).put(updatePost)
export default router

Por último crearemos los controladores de cada una de las peticiones en /src/controllers/posts.controllers.ts

import { Request, Response } from "express"
import { connect } from "../database"
import { IPost } from "../interface/Post.interface"
export async function getPosts(req: Request, res: Response): Promise<Response> {
const db = await connect()
const posts = await db.query("SELECT * FROM posts")
return res.json(posts)
}
export async function createPost(req: Request, res: Response) {
const newPost: IPost = req.body
const db = await connect()
await db.query("INSERT INTO posts SET ?", [newPost])
return res.json({
message: "Post Created",
})
}
export async function getPost(req: Request, res: Response): Promise<Response> {
const id = req.params.postId
const db = await connect()
const post = await db.query("SELECT * FROM posts WHERE id = ?", [id])
return res.json(post[0])
}
export async function deletePost(
req: Request,
res: Response
): Promise<Response> {
const id = req.params.postId
const db = await connect()
const post = await db.query("DELETE FROM posts WHERE id = ?", [id])
return res.json({
message: "Post deleted",
})
}
export async function updatePost(
req: Request,
res: Response
): Promise<Response> {
const id = req.params.postId
const updatePost: IPost = req.body
const db = await connect()
const post = await db.query("UPDATE posts set ? WHERE id = ?", [
updatePost,
id,
])
return res.json({
message: "Post updated",
})
}