1646739263
En este tutorial, aprenderemos cómo crear e implementar un backend de aplicación de administración de imágenes.
Podrá almacenar un registro de una imagen en la base de datos, recuperar el registro de la imagen de la base de datos, actualizar el registro e incluso eliminar el registro por completo, según sea el caso.
Para lograr todo esto, usaremos Express (un marco Node.js), Postgres (una base de datos), Cloudinary (un almacenamiento de imágenes basado en la nube), GitHub (para el control/almacenamiento de versiones) y Heroku (una plataforma de alojamiento).
Todas estas herramientas son gratuitas. Así que no tienes que preocuparte por cómo pagarlos. Gracias a estos grandes innovadores.
Si es nuevo en la mayoría de estas tecnologías, le aconsejo que siga mi otro tutorial sobre cómo crear un servidor y cargar imágenes en Cloudinary .
Si está totalmente interesado en Postgres, consulte este tutorial .
Cuando estés listo, ¡manos a la obra!
Por lo tanto, querrá comenzar clonando este proyecto si aún no lo tiene.
En tu pgAdmin :
tutorial
tutorial
tutorial
. (No olvides darle todos los privilegios.)De vuelta en el directorio de su proyecto, instale los paquetes node-postgres ( npm i pg
) y make-runnable ( npm i make-runnable
).
En su package.json
archivo, reemplace el contenido de "scripts"
con "create": "node ./services/dbConnect createTables"
. Usaremos esto para ejecutar el dbConnect
archivo que estamos a punto de crear.
Cree un services/dbConnect
archivo que contenga el siguiente código:
const pg = require("pg");
const config = {
user: "tutorial",
database: "tutorial",
password: "tutorial",
port: 5432,
max: 10, // max number of clients in the pool
idleTimeoutMillis: 30000,
};
const pool = new pg.Pool(config);
pool.on("connect", () => {
console.log("connected to the Database");
});
const createTables = () => {
const imageTable = `CREATE TABLE IF NOT EXISTS
images(
id SERIAL PRIMARY KEY,
title VARCHAR(128) NOT NULL,
cloudinary_id VARCHAR(128) NOT NULL,
image_url VARCHAR(128) NOT NULL
)`;
pool
.query(imageTable)
.then((res) => {
console.log(res);
pool.end();
})
.catch((err) => {
console.log(err);
pool.end();
});
};
pool.on("remove", () => {
console.log("client removed");
process.exit(0);
});
//export pool and createTables to be accessible from anywhere within the application
module.exports = {
createTables,
pool,
};
require("make-runnable");
Ahora estamos listos para crear la tabla en nuestra base de datos. Si estás listo, ¡vamos al rock and roll!
Ejecuta el siguiente código en tu terminal:
npm run create
Si la imagen a continuación es su resultado, entonces está listo para comenzar:
Verifique su pgAdmin , y debería tener su mesa asentada correctamente en su base de datos como en la imagen a continuación:
Muy bien, ha sido un largo camino. Es hora de unir Node, Postgres y Cloudinary.
Primero, requiere el dbConnect.js
archivo en la parte superior del app.js
archivo así:
const db = require('services/dbConnect.js');
Luego, en el app.js
archivo, cree un nuevo punto final (imagen persistente) con el siguiente código:
// persist image
app.post("/persist-image", (request, response) => {
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image,
}
// upload image here
cloudinary.uploader.upload(data.image)
.then().catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
})
Reemplace el then
bloque con el siguiente código:
.then((image) => {
db.pool.connect((err, client) => {
// inset query to run if the upload to cloudinary is successful
const insertQuery = 'INSERT INTO images (title, cloudinary_id, image_url)
VALUES($1,$2,$3) RETURNING *';
const values = [data.title, image.public_id, image.secure_url];
})
})
Los image.public_id
y image.secure_url
se obtienen como parte de los detalles devueltos para una imagen después de que la imagen se haya cargado correctamente en Cloudinary.
Ahora mantenemos un registro de image.public_id
y image.secure_url
(como puede ver en el código anterior) para usarlo para recuperar, actualizar o eliminar el registro de la imagen cuando lo creamos conveniente.
Muy bien, ¡sigamos adelante!
Aún en el then
bloque, agregue el siguiente código debajo de la consulta que creamos:
// execute query
client.query(insertQuery, values)
.then((result) => {
result = result.rows[0];
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Uploaded Successfully",
title: result.title,
cloudinary_id: result.cloudinary_id,
image_url: result.image_url,
},
})
}).catch((e) => {
response.status(500).send({
message: "failure",
e,
});
})
Así que nuestro persist-image
punto final ahora se ve así:
// persist image
app.post("/persist-image", (request, response) => {
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image
}
// upload image here
cloudinary.uploader.upload(data.image)
.then((image) => {
db.pool.connect((err, client) => {
// inset query to run if the upload to cloudinary is successful
const insertQuery = 'INSERT INTO images (title, cloudinary_id, image_url)
VALUES($1,$2,$3) RETURNING *';
const values = [data.title, image.public_id, image.secure_url];
// execute query
client.query(insertQuery, values)
.then((result) => {
result = result.rows[0];
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Uploaded Successfully",
title: result.title,
cloudinary_id: result.cloudinary_id,
image_url: result.image_url,
},
})
}).catch((e) => {
response.status(500).send({
message: "failure",
e,
});
})
})
}).catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
});
Ahora vamos a probar todo nuestro arduo trabajo:
Abra su cartero y pruebe su punto final como se muestra en la imagen a continuación. El mío fue un éxito. ¿Espero que el tuyo tampoco haya tenido errores?
Abra su consola/panel de Cloudinary y verifique su media Library
. Su nueva imagen debería estar sentada allí cómodamente como la mía a continuación:
Y ahora a la razón principal por la que estamos aquí: verifique la images
tabla en su pgAdmin . La mía es la que ves a continuación:
¡Oh, la la! Llegamos hasta aquí. Por favor, tómese un descanso si lo necesita. Estaré aquí esperándote cuando regreses. :)
Si está listo, recuperemos la imagen que conservamos hace un momento.
Comience con este código:
app.get("/retrieve-image/:cloudinary_id", (request, response) => {
});
A continuación, necesitaremos recopilar una identificación única del usuario para recuperar una imagen en particular. Así que agregue const { id } = request.params;
al código anterior así:
app.get("/retrieve-image/:cloudinary_id", (request, response) => {
// data from user
const { cloudinary_id } = request.params;
});
Agregue el siguiente código justo debajo del código anterior:
db.pool.connect((err, client) => {
// query to find image
const query = "SELECT * FROM images WHERE cloudinary_id = $1";
const value = [cloudinary_id];
});
Debajo de la consulta, ejecute la consulta con el siguiente código:
// execute query
client
.query(query, value)
.then((output) => {
response.status(200).send({
status: "success",
data: {
id: output.rows[0].cloudinary_id,
title: output.rows[0].title,
url: output.rows[0].image_url,
},
});
})
.catch((error) => {
response.status(401).send({
status: "failure",
data: {
message: "could not retrieve record!",
error,
},
});
});
Ahora nuestra retrieve-image
API se ve así:
app.get("/retrieve-image/:cloudinary_id", (request, response) => {
// data from user
const { cloudinary_id } = request.params;
db.pool.connect((err, client) => {
// query to find image
const query = "SELECT * FROM images WHERE cloudinary_id = $1";
const value = [cloudinary_id];
// execute query
client
.query(query, value)
.then((output) => {
response.status(200).send({
status: "success",
data: {
id: output.rows[0].cloudinary_id,
title: output.rows[0].title,
url: output.rows[0].image_url,
},
});
})
.catch((error) => {
response.status(401).send({
status: "failure",
data: {
message: "could not retrieve record!",
error,
},
});
});
});
});
A ver qué tan bien lo hicimos:
En su cartero, copie cloudinary_id
y agréguelo a la URL como en la imagen a continuación:
¡SÍ SÍ! También podemos recuperar nuestra imagen.
Si estás aquí, te mereces un aplauso y una ovación de pie por tu laboriosidad.
¡Felicidades! Acabas de alcanzar un gran hito.
El código para almacenar y recuperar registros de imágenes está aquí .
Ahora veremos cómo eliminar y actualizar un registro de imagen según sea el caso. Comencemos con el punto final de eliminación.
En el archivo app.js, comience con el siguiente código:
// delete image
app.delete("delete-image/:cloudinary_id", (request, response) => {
});
A continuación, queremos obtener el ID único de la imagen que queremos eliminar de la URL, es decir, cloudinary_id
. Entonces, dentro del código anterior, agregue:
const { cloudinary_id } = request.params;
Ahora comenzamos el proceso de eliminación.
Primero, eliminamos de Cloudinary. Agregue el siguiente código para eliminar la imagen de Cloudinary:
cloudinary.uploader
.destroy(cloudinary_id)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "Failure",
error,
});
});
En este punto, nuestra API solo puede eliminar la imagen de Cloudinary (puede consultarla en cartero). Pero también queremos deshacernos del registro que tenemos en nuestra base de datos de Postgres.
En segundo lugar, eliminamos de nuestra base de datos de Postgres. Para hacerlo, reemplace el código en el then
bloque con lo siguiente query
:
db.pool.connect((err, client) => {
// delete query
const deleteQuery = "DELETE FROM images WHERE cloudinary_id = $1";
const deleteValue = [cloudinary_id];
})
Ejecute la consulta con el siguiente código debajo:
// execute delete query
client.query(deleteQuery, deleteValue)
.then((deleteResult) => {
response.status(200).send({
message: "Image Deleted Successfully!",
deleteResult
});
}).catch((e) => {
response.status(500).send({
message: "Image Couldn't be Deleted!",
e
});
});
Así que nuestro Endpoint debería verse así:
// delete image
app.delete("/delete-image/:cloudinary_id", (request, response) => {
// unique ID
const { cloudinary_id } = request.params;
// delete image from cloudinary first
cloudinary.uploader
.destroy(cloudinary_id)
// delete image record from postgres also
.then(() => {
db.pool.connect((err, client) => {
// delete query
const deleteQuery = "DELETE FROM images WHERE cloudinary_id = $1";
const deleteValue = [cloudinary_id];
// execute delete query
client
.query(deleteQuery, deleteValue)
.then((deleteResult) => {
response.status(200).send({
message: "Image Deleted Successfully!",
deleteResult,
});
})
.catch((e) => {
response.status(500).send({
message: "Image Couldn't be Deleted!",
e,
});
});
})
})
.catch((error) => {
response.status(500).send({
message: "Failure",
error,
});
});
});
Ha llegado el momento de que pongamos a prueba nuestro Endpoint.
El siguiente es mi Cloudinary media library
con dos imágenes que ya subí. Tome nota de su identificación única ( public_id
).
Si aún no lo tiene, utilice el extremo de imagen persistente para cargar algunas imágenes.
Ahora procedamos al cartero:
Observe, la identificación única, ya que coincide con una de las imágenes en mi biblioteca de medios de Cloudinary.
Desde la salida, ejecutamos el comando DELETE y eso eliminó una FILA de nuestra TABLA de imágenes en nuestra base de datos.
Ahora esta es mi biblioteca de medios con una de las imágenes restantes:
Walahhhh... Ahora podemos deshacernos de una imagen.
Tómese un descanso si lo necesita. ✌🏾
Si está listo, estoy listo para actualizar las imágenes.
Debajo de la delete-image
API, comencemos a crear la update-image
API con el siguiente código:
// update image
app.put("/update-image/:cloudinary_id", (request, response) => {
});
All codes will live in there.
Recopile la identificación única de Cloudinary y los detalles de la nueva imagen del usuario con el siguiente código:
// unique ID
const { cloudinary_id } = request.params;
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image,
};
Elimina la imagen de Cloudinary con el siguiente código:
// delete image from cloudinary first
cloudinary.uploader
.destroy(cloudinary_id)
// upload image here
.then()
.catch((error) => {
response.status(500).send({
message: "failed",
error,
});
});
A continuación, cargue otra imagen en Cloudinary. Para hacer eso, ingrese el siguiente código en el then
bloque:
() => {
cloudinary.uploader
.upload(data.image)
.then()
.catch((err) => {
response.status(500).send({
message: "failed",
err,
});
});
}
Ahora reemplacemos nuestro registro inicial con los nuevos detalles de la imagen. Reemplace el contenido del then
bloque con lo siguiente:
(result) => {
db.pool.connect((err, client) => {
// update query
const updateQuery =
"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4";
const value = [
data.title,
result.public_id,
result.secure_url,
cloudinary_id,
];
});
}
Ejecutamos la consulta usando el siguiente código justo debajo de la declaración de la consulta:
// execute query
client
.query(updateQuery, value)
.then(() => {
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Updated Successfully"
},
});
})
.catch((e) => {
response.status(500).send({
message: "Update Failed",
e,
});
});
En este punto, esto es lo que tengo:
// update image
app.put("/update-image/:cloudinary_id", (request, response) => {
// unique ID
const { cloudinary_id } = request.params;
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image,
};
// delete image from cloudinary first
cloudinary.uploader
.destroy(cloudinary_id)
// upload image here
.then(() => {
cloudinary.uploader
.upload(data.image)
// update the database here
.then((result) => {
db.pool.connect((err, client) => {
// update query
const updateQuery =
"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4";
const value = [
data.title,
result.public_id,
result.secure_url,
cloudinary_id,
];
// execute query
client
.query(updateQuery, value)
.then(() => {
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Updated Successfully"
},
});
})
.catch((e) => {
response.status(500).send({
message: "Update Failed",
e,
});
});
});
})
.catch((err) => {
response.status(500).send({
message: "failed",
err,
});
});
})
.catch((error) => {
response.status(500).send({
message: "failed",
error,
});
});
});
¡Es tiempo de prueba!
Este es mi cartero en la imagen de abajo:
Tome nota de la ID única de Cloudinary que coincide con la imagen que queda en mi biblioteca de medios de Cloudinary.
Ahora eche un vistazo a mi biblioteca de medios Cloudinary en la imagen que sigue:
Tome nota de la nueva imagen que reemplaza a la inicial en mi biblioteca de medios arriba.
Además, vea que la ID única de Cloudinary coincida con la de mi base de datos con el nuevo título. Ver imagen a continuación:
¡Yayeh! ¡Lo hiciste increíblemente bien! 💪
Acabamos de completar una aplicación de gestión de imágenes con Node.js, Cloudinary y Postgres.
Express Routing nos permite optimizar nuestro código Node.js o darle una estructura más modular al separar la lógica comercial de los controladores. Queremos usar eso para limpiar nuestro código hasta ahora.
Comenzaremos creando una nueva carpeta con el nombre routes
en el directorio raíz:
mk dir routes
En la carpeta de rutas, crea un archivo con el nombre: routes.js
.
Para ventanas:
echo . > routes.js
Para Mac:
touch routes.js
Vacíe el routes.js
archivo si hay algo e ingrese el siguiente código:
const express = require('express');
const router = express.Router();
module.exports = router;
Agrega el siguiente código arriba de la última línea:
const cloudinary = require("cloudinary").v2;
require("dotenv").config();
const db = require("../services/dbConnect.js");
// cloudinary configuration
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
De vuelta en el archivo App.js, elimine el siguiente código:
const cloudinary = require("cloudinary").v2;
require("dotenv").config();
const db = require("./services/dbConnect.js");
// cloudinary configuration
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
Mueva todas las API a routes.js
.
Cambie todas las apariciones de app
a con router
cuidado.
Mi routes.js
archivo ahora se ve así .
De vuelta en el app.js
archivo, importe el routes.js
archivo así:
// import the routes file
const routes = require("./routes/routes")
Ahora registre las rutas así:
// register the routes
app.use('/', routes);
Este es mi app.js
archivo en este momento:
const express = require("express");
const app = express();
// import the routes file
const routes = require("./routes/routes")
// body parser configuration
const bodyParser = require("body-parser");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// register the routes
app.use('/', routes);
module.exports = app;
Es hora de probar y ver si nuestras rutas siguen funcionando como antes.
Asegúrate de que el tuyo funcione como el mío a continuación:
¡Guau! Hemos podido separar nuestras rutas de nuestro app.js
archivo.
El código para esto está aquí .
Aunque nuestro routes.js
archivo todavía es largo, tenemos una buena base para separar nuestra lógica comercial de nuestros controladores. Ha llegado el momento de hacer precisamente eso.
Comience creando una nueva carpeta en la routes
carpeta y asígnele el nombre controllers
.
En la carpeta de controladores, cree 5 archivos y asígneles el nombre de los 5 puntos finales.
Nuestra carpeta y archivos deben estar estructurados de la siguiente manera:
Volviendo al archivo route.js, trabajemos en la image-upload
API. Cortar el siguiente código:
(request, response) => {
// collected image from a user
const data = {
image: request.body.image,
};
// upload image here
cloudinary.uploader
.upload(data.image)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
}
En el imageUpload
archivo, equipare el código que ya cortó desde el image-upload
punto final para que le exports.imageUpload
guste:
exports.imageUpload = (request, response) => {
// collected image from a user
const data = {
image: request.body.image,
};
// upload image here
cloudinary.uploader
.upload(data.image)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
}
Ahora importemos lo necesario para que este código funcione. Así que este es mi imageUpload
archivo en este momento:
const cloudinary = require("cloudinary").v2;
require("dotenv").config();
// cloudinary configuration
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
exports.imageUpload = (request, response) => {
// collected image from a user
const data = {
image: request.body.image,
};
// upload image here
cloudinary.uploader
.upload(data.image)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
}
Importemos y registremos la imageUpload
API en el routes.js
archivo así:
const imageUpload = require("./controllers/imageUpload");
// image upload API
router.post("image-upload", imageUpload.imageUpload);
Ahora tenemos esta línea de código que apunta a la imageUpload
API en el imageUpload.js
archivo del routes.js
archivo.
¡Qué asombroso! Nuestro código es más legible.
Asegúrese de probar la API para asegurarse de que funciona correctamente. El mío funciona perfectamente. Ver imagen a continuación:
¡Ahora es tu turno!
Aplique lo que ha aprendido a las otras API. Veamos lo que tienes.
Te estaré esperando del otro lado...
Si estás aquí, entonces creo que has hecho el tuyo y están funcionando perfectamente, o al menos, ya diste lo mejor de ti. ¡Prestigio!
Mira el mío aquí .
Felicidades. Usted es maravilloso :)
El código de optimización de código está aquí .
Muy bien, al siguiente paso.
Ahora que hemos completado nuestra aplicación, implementémosla en Heroku para que podamos acceder a ella incluso sin estar en nuestra computadora portátil donde se escribió el código.
Lo guiaré a través de la carga de nuestra aplicación en GitHub y su implementación en Heroku .
Sin más preámbulos, ensuciémonos las manos.
Subir o subir a GitHub es tan fácil como comer tu comida favorita. Consulte este recurso para aprender a enviar su proyecto desde su máquina local a GitHub.
Comencemos por crear una cuenta en Heroku .
Si ha creado una cuenta, es posible que se le haya pedido que cree una aplicación (es decir, una carpeta donde se alojará su aplicación). Puedes hacer eso, pero yo haré el mío usando mi terminal ya que el terminal viene con algunas funcionalidades adicionales que necesitaremos más adelante.
Abra su proyecto en una terminal si aún no lo ha hecho. Usaré el terminal integrado VS Code.
Instale la CLI de Heroku:
npm install heroku
Inicie sesión en la CLI de Heroku. Esto abrirá una ventana del navegador, que puede usar para iniciar sesión.
heroku login
Crea una aplicación. Puede tener cualquier nombre. estoy node-postgres-cloudinary
usando
heroku create node-postgres-cloudinary
Vaya a su tablero de Heroku y encontrará la aplicación recién creada.
Waalaah!
Así es como se ve el mío en la imagen de arriba. Ya tengo algunas aplicaciones allí, pero puedes ver la que acabo de crear.
Ahora agreguemos la base de datos PostgreSQL a la aplicación.
Haga clic en la aplicación que acaba de crear. Te llevará al tablero de la aplicación.
Haga clic en la Resources
pestaña/menú.
En la Add-ons
Sección, busque y seleccione Heroku Postgres
.
Asegúrese de seleccionar el Hobby Dev - Free
plan en la ventana emergente que aparece a continuación:
Haga clic en el provision
botón para agregarlo a la aplicación de la siguiente manera:
Haga clic en Heroku Postgres
para ir al Heroku Postgres
tablero.
Haga clic en la settings
pestaña:
Haga clic en View Credentials
:
En las Credenciales, nos interesa la CLI de Heroku. Lo usaremos en un momento.
De vuelta a la terminal.
Confirmemos si Heroku Postgres
se agregó con éxito. Introduce lo siguiente en la terminal:
heroku addons
¡Síiiiiiiiii! Se agregó con éxito.
Antes de continuar, asegúrese de que su PostgreSQL path
esté configurado correctamente si está en Windows . Siga este enlace para aprender a configurar un archivo path
. La ruta debe ser así: C:\Program Files\PostgreSQL\<VERSION>\bin
.
La versión dependerá de la instalada en su máquina. El mío es: C:\Program Files\PostgreSQL\12\bin
ya que estoy usando el version 12
.
La siguiente imagen podría ser útil:
Es posible que deba navegar a la carpeta donde está instalado PostgreSQL en su máquina para encontrar su propia ruta.
Inicie sesión en el Heroku Postgres
uso de la CLI de Heroku desde nuestras Heroku Postgres
credenciales. Este es el mío, el tuyo será diferente:
heroku pg:psql postgresql-slippery-19135 --app node-postgres-cloudinary
Si recibió un error, lo más probable es que su ruta no esté configurada correctamente.
Por el momento, mi base de datos se ve así:
const pg = require("pg");
const config = {
user: "tutorial",
database: "tutorial",
password: "tutorial",
port: 5432,
max: 10, // max number of clients in the pool
idleTimeoutMillis: 30000,
};
const pool = new pg.Pool(config);
pool.on("connect", () => {
console.log("connected to the Database");
});
Si intenta conectar Heroku a esto, obtendrá un error. Esto se debe a que Heroku ya tiene una connection string
configuración. Así que tenemos que configurar nuestra conexión para que Heroku pueda conectarse fácilmente.
Voy a refactorizar mi archivo de conexión a la base de datos ( dbConnect.js
) y .env
el archivo para que esto suceda.
const pg = require('pg');
require('dotenv').config();
// set production variable. This will be called when deployed to a live host
const isProduction = process.env.NODE_ENV === 'production';
// configuration details
const connectionString = `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_DATABASE}`;
// if project has been deployed, connect with the host's DATABASE_URL
// else connect with the local DATABASE_URL
const pool = new pg.Pool({
connectionString: isProduction ? process.env.DATABASE_URL : connectionString,
ssl: isProduction,
});
// display message on success if successful
pool.on('connect', () => {
console.log('Teamwork Database connected successfully!');
});
DB_USER="tutorial"
DB_PASSWORD="tutorial"
DB_HOST="localhost"
DB_PORT="5432"
DB_DATABASE="tutorial"
Con la configuración del archivo dbconnect
y .env
, estamos listos para exportar nuestra base de datos y tablas desde nuestra máquina local a heroku postgres
.
Vaya a su pgAdmin
y localice la base de datos para este tutorial. El mío es un tutorial.
Haga clic derecho sobre él y seleccione Backup
. Esto abrirá una nueva ventana.
Ingrese un nombre para el archivo SQL como lo hice yo. Seleccione el plain
formato. Luego haga clic en Copia de seguridad. Esto guardará el archivo en su carpeta de documentos.
Localice el archivo y muévalo al directorio del proyecto. Puede estar en cualquier lugar del directorio, pero elijo mover el mío al services
directorio porque contiene los archivos relacionados con la base de datos.
De vuelta en la terminal, navegue a la carpeta que contiene el archivo SQL y ejecute el siguiente código para agregar las tablas que acabamos de exportar a la base de heroku postgres
datos:
cat <your-SQL-file> | <heroku-CLI-from-the-heroku-posgres-credentials>
Así es como se ve el mío:
cat tutorial.sql | heroku pg:psql postgresql-slippery-19135 --app node-postgres-cloudinary
¿Notaste que cambié el directorio a servicios ( cd services
)? Ahí es donde se encuentra mi sql
archivo.
¡Guau! Acabamos de exportar con éxito nuestra base de datos y tablas a Heroku.
ya casi termina...
Agregue los archivos en los que hemos realizado cambios:
$ git add .
El punto ( .
) agrega todos los archivos.
Confirme sus últimos cambios:
$ git commit -m "refactored the dbConnect and .env file to fit in to heroku; Added the database SQL file"
Empuje los archivos confirmados:
$ git push origin -u master
Ve al tablero de tu aplicación:
Seleccione el método de implementación de GitHub:
Busque y seleccione un repositorio, y haga clic en connect
:
Seleccione la rama que desea implementar (en mi caso, es la master
rama):
Habilite la implementación automática haciendo clic en el Enable automatic deployment
botón como en la imagen de arriba.
Haga clic en el Deploy
botón en el despliegue manual
No tendremos que hacer todo esto para implementaciones posteriores.
Ahora tiene un botón que le indica "ver el sitio" después de completar la compilación. Pinchalo. Esto abrirá su aplicación en una nueva pestaña.
¡Oh, no! ¿Un insecto? ¿¿Error de la aplicación??
No te preocupes, es solo un pequeño problema. Algo que nunca debe olvidar hacer al realizar implementaciones. La mayoría de los servicios de alojamiento lo requerirán.
Regrese al directorio raíz de su proyecto.
Cree un archivo y asígnele un nombre Procfile
(no tiene extensión).
En el archivo, ingrese el siguiente código:
web: node index.js
Esto dirige a Heroku al archivo del servidor ( index.js
), que es el punto de entrada de la aplicación. Si su servidor está en un archivo diferente, modifíquelo según sea necesario.
Guarde el archivo y envíe los nuevos cambios a GitHub.
Espere de 2 a 5 minutos para que Heroku detecte automáticamente los cambios en su repositorio de GitHub y represente los cambios en la aplicación.
Ahora puede actualizar esa página de error y ver que su arduo trabajo da sus frutos:
También puede probar la retrieve image
ruta y verla funcionando.
¡Felicidades! Qué hazaña has logrado.
Otras rutas (persistir-imagen, actualizar-imagen y eliminar-imagen) no funcionarán porque no hemos aprovisionado o agregado cloudinary
complementos. Es tan sencillo como el PostgreSQL
que acabamos de hacer. Así que puedes darle una oportunidad.
Comenzamos este tutorial con el objetivo de aprender cómo construir una aplicación de backend usando Express, Postgres, Cloudinary, Github y Heroku.
Aprendimos cómo almacenar, recuperar, eliminar y actualizar un registro de imagen. Luego organizamos nuestro código con Express Routing, lo subimos a GitHub y lo implementamos en Heroku. Eso fue mucho.
Espero que estén de acuerdo en que valió la pena porque aprendimos mucho. Debe intentar agregar el complemento Cloudinary usted mismo para mejorar aún más su conocimiento.
¡Gracias por leer!
Enlace: https://www.freecodecamp.org/news/how-to-build-a-backend-application/
#express #postgres #cloudinary #github #heroku
1651383480
This serverless plugin is a wrapper for amplify-appsync-simulator made for testing AppSync APIs built with serverless-appsync-plugin.
Install
npm install serverless-appsync-simulator
# or
yarn add serverless-appsync-simulator
Usage
This plugin relies on your serverless yml file and on the serverless-offline
plugin.
plugins:
- serverless-dynamodb-local # only if you need dynamodb resolvers and you don't have an external dynamodb
- serverless-appsync-simulator
- serverless-offline
Note: Order is important serverless-appsync-simulator
must go before serverless-offline
To start the simulator, run the following command:
sls offline start
You should see in the logs something like:
...
Serverless: AppSync endpoint: http://localhost:20002/graphql
Serverless: GraphiQl: http://localhost:20002
...
Configuration
Put options under custom.appsync-simulator
in your serverless.yml
file
| option | default | description | | ------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | apiKey | 0123456789
| When using API_KEY
as authentication type, the key to authenticate to the endpoint. | | port | 20002 | AppSync operations port; if using multiple APIs, the value of this option will be used as a starting point, and each other API will have a port of lastPort + 10 (e.g. 20002, 20012, 20022, etc.) | | wsPort | 20003 | AppSync subscriptions port; if using multiple APIs, the value of this option will be used as a starting point, and each other API will have a port of lastPort + 10 (e.g. 20003, 20013, 20023, etc.) | | location | . (base directory) | Location of the lambda functions handlers. | | refMap | {} | A mapping of resource resolutions for the Ref
function | | getAttMap | {} | A mapping of resource resolutions for the GetAtt
function | | importValueMap | {} | A mapping of resource resolutions for the ImportValue
function | | functions | {} | A mapping of external functions for providing invoke url for external fucntions | | dynamoDb.endpoint | http://localhost:8000 | Dynamodb endpoint. Specify it if you're not using serverless-dynamodb-local. Otherwise, port is taken from dynamodb-local conf | | dynamoDb.region | localhost | Dynamodb region. Specify it if you're connecting to a remote Dynamodb intance. | | dynamoDb.accessKeyId | DEFAULT_ACCESS_KEY | AWS Access Key ID to access DynamoDB | | dynamoDb.secretAccessKey | DEFAULT_SECRET | AWS Secret Key to access DynamoDB | | dynamoDb.sessionToken | DEFAULT_ACCESS_TOKEEN | AWS Session Token to access DynamoDB, only if you have temporary security credentials configured on AWS | | dynamoDb.* | | You can add every configuration accepted by DynamoDB SDK | | rds.dbName | | Name of the database | | rds.dbHost | | Database host | | rds.dbDialect | | Database dialect. Possible values (mysql | postgres) | | rds.dbUsername | | Database username | | rds.dbPassword | | Database password | | rds.dbPort | | Database port | | watch | - *.graphql
- *.vtl | Array of glob patterns to watch for hot-reloading. |
Example:
custom:
appsync-simulator:
location: '.webpack/service' # use webpack build directory
dynamoDb:
endpoint: 'http://my-custom-dynamo:8000'
Hot-reloading
By default, the simulator will hot-relad when changes to *.graphql
or *.vtl
files are detected. Changes to *.yml
files are not supported (yet? - this is a Serverless Framework limitation). You will need to restart the simulator each time you change yml files.
Hot-reloading relies on watchman. Make sure it is installed on your system.
You can change the files being watched with the watch
option, which is then passed to watchman as the match expression.
e.g.
custom:
appsync-simulator:
watch:
- ["match", "handlers/**/*.vtl", "wholename"] # => array is interpreted as the literal match expression
- "*.graphql" # => string like this is equivalent to `["match", "*.graphql"]`
Or you can opt-out by leaving an empty array or set the option to false
Note: Functions should not require hot-reloading, unless you are using a transpiler or a bundler (such as webpack, babel or typescript), un which case you should delegate hot-reloading to that instead.
Resource CloudFormation functions resolution
This plugin supports some resources resolution from the Ref
, Fn::GetAtt
and Fn::ImportValue
functions in your yaml file. It also supports some other Cfn functions such as Fn::Join
, Fb::Sub
, etc.
Note: Under the hood, this features relies on the cfn-resolver-lib package. For more info on supported cfn functions, refer to the documentation
You can reference resources in your functions' environment variables (that will be accessible from your lambda functions) or datasource definitions. The plugin will automatically resolve them for you.
provider:
environment:
BUCKET_NAME:
Ref: MyBucket # resolves to `my-bucket-name`
resources:
Resources:
MyDbTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: myTable
...
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-bucket-name
...
# in your appsync config
dataSources:
- type: AMAZON_DYNAMODB
name: dynamosource
config:
tableName:
Ref: MyDbTable # resolves to `myTable`
Sometimes, some references cannot be resolved, as they come from an Output from Cloudformation; or you might want to use mocked values in your local environment.
In those cases, you can define (or override) those values using the refMap
, getAttMap
and importValueMap
options.
refMap
takes a mapping of resource name to value pairsgetAttMap
takes a mapping of resource name to attribute/values pairsimportValueMap
takes a mapping of import name to values pairsExample:
custom:
appsync-simulator:
refMap:
# Override `MyDbTable` resolution from the previous example.
MyDbTable: 'mock-myTable'
getAttMap:
# define ElasticSearchInstance DomainName
ElasticSearchInstance:
DomainEndpoint: 'localhost:9200'
importValueMap:
other-service-api-url: 'https://other.api.url.com/graphql'
# in your appsync config
dataSources:
- type: AMAZON_ELASTICSEARCH
name: elasticsource
config:
# endpoint resolves as 'http://localhost:9200'
endpoint:
Fn::Join:
- ''
- - https://
- Fn::GetAtt:
- ElasticSearchInstance
- DomainEndpoint
In some special cases you will need to use key-value mock nottation. Good example can be case when you need to include serverless stage value (${self:provider.stage}
) in the import name.
This notation can be used with all mocks - refMap
, getAttMap
and importValueMap
provider:
environment:
FINISH_ACTIVITY_FUNCTION_ARN:
Fn::ImportValue: other-service-api-${self:provider.stage}-url
custom:
serverless-appsync-simulator:
importValueMap:
- key: other-service-api-${self:provider.stage}-url
value: 'https://other.api.url.com/graphql'
This plugin only tries to resolve the following parts of the yml tree:
provider.environment
functions[*].environment
custom.appSync
If you have the need of resolving others, feel free to open an issue and explain your use case.
For now, the supported resources to be automatically resovled by Ref:
are:
Feel free to open a PR or an issue to extend them as well.
External functions
When a function is not defined withing the current serverless file you can still call it by providing an invoke url which should point to a REST method. Make sure you specify "get" or "post" for the method. Default is "get", but you probably want "post".
custom:
appsync-simulator:
functions:
addUser:
url: http://localhost:3016/2015-03-31/functions/addUser/invocations
method: post
addPost:
url: https://jsonplaceholder.typicode.com/posts
method: post
Supported Resolver types
This plugin supports resolvers implemented by amplify-appsync-simulator
, as well as custom resolvers.
From Aws Amplify:
Implemented by this plugin
#set( $cols = [] )
#set( $vals = [] )
#foreach( $entry in $ctx.args.input.keySet() )
#set( $regex = "([a-z])([A-Z]+)")
#set( $replacement = "$1_$2")
#set( $toSnake = $entry.replaceAll($regex, $replacement).toLowerCase() )
#set( $discard = $cols.add("$toSnake") )
#if( $util.isBoolean($ctx.args.input[$entry]) )
#if( $ctx.args.input[$entry] )
#set( $discard = $vals.add("1") )
#else
#set( $discard = $vals.add("0") )
#end
#else
#set( $discard = $vals.add("'$ctx.args.input[$entry]'") )
#end
#end
#set( $valStr = $vals.toString().replace("[","(").replace("]",")") )
#set( $colStr = $cols.toString().replace("[","(").replace("]",")") )
#if ( $valStr.substring(0, 1) != '(' )
#set( $valStr = "($valStr)" )
#end
#if ( $colStr.substring(0, 1) != '(' )
#set( $colStr = "($colStr)" )
#end
{
"version": "2018-05-29",
"statements": ["INSERT INTO <name-of-table> $colStr VALUES $valStr", "SELECT * FROM <name-of-table> ORDER BY id DESC LIMIT 1"]
}
#set( $update = "" )
#set( $equals = "=" )
#foreach( $entry in $ctx.args.input.keySet() )
#set( $cur = $ctx.args.input[$entry] )
#set( $regex = "([a-z])([A-Z]+)")
#set( $replacement = "$1_$2")
#set( $toSnake = $entry.replaceAll($regex, $replacement).toLowerCase() )
#if( $util.isBoolean($cur) )
#if( $cur )
#set ( $cur = "1" )
#else
#set ( $cur = "0" )
#end
#end
#if ( $util.isNullOrEmpty($update) )
#set($update = "$toSnake$equals'$cur'" )
#else
#set($update = "$update,$toSnake$equals'$cur'" )
#end
#end
{
"version": "2018-05-29",
"statements": ["UPDATE <name-of-table> SET $update WHERE id=$ctx.args.input.id", "SELECT * FROM <name-of-table> WHERE id=$ctx.args.input.id"]
}
{
"version": "2018-05-29",
"statements": ["UPDATE <name-of-table> set deleted_at=NOW() WHERE id=$ctx.args.id", "SELECT * FROM <name-of-table> WHERE id=$ctx.args.id"]
}
#set ( $index = -1)
#set ( $result = $util.parseJson($ctx.result) )
#set ( $meta = $result.sqlStatementResults[1].columnMetadata)
#foreach ($column in $meta)
#set ($index = $index + 1)
#if ( $column["typeName"] == "timestamptz" )
#set ($time = $result["sqlStatementResults"][1]["records"][0][$index]["stringValue"] )
#set ( $nowEpochMillis = $util.time.parseFormattedToEpochMilliSeconds("$time.substring(0,19)+0000", "yyyy-MM-dd HH:mm:ssZ") )
#set ( $isoDateTime = $util.time.epochMilliSecondsToISO8601($nowEpochMillis) )
$util.qr( $result["sqlStatementResults"][1]["records"][0][$index].put("stringValue", "$isoDateTime") )
#end
#end
#set ( $res = $util.parseJson($util.rds.toJsonString($util.toJson($result)))[1][0] )
#set ( $response = {} )
#foreach($mapKey in $res.keySet())
#set ( $s = $mapKey.split("_") )
#set ( $camelCase="" )
#set ( $isFirst=true )
#foreach($entry in $s)
#if ( $isFirst )
#set ( $first = $entry.substring(0,1) )
#else
#set ( $first = $entry.substring(0,1).toUpperCase() )
#end
#set ( $isFirst=false )
#set ( $stringLength = $entry.length() )
#set ( $remaining = $entry.substring(1, $stringLength) )
#set ( $camelCase = "$camelCase$first$remaining" )
#end
$util.qr( $response.put("$camelCase", $res[$mapKey]) )
#end
$utils.toJson($response)
Variable map support is limited and does not differentiate numbers and strings data types, please inject them directly if needed.
Will be escaped properly: null
, true
, and false
values.
{
"version": "2018-05-29",
"statements": [
"UPDATE <name-of-table> set deleted_at=NOW() WHERE id=:ID",
"SELECT * FROM <name-of-table> WHERE id=:ID and unix_timestamp > $ctx.args.newerThan"
],
variableMap: {
":ID": $ctx.args.id,
## ":TIMESTAMP": $ctx.args.newerThan -- This will be handled as a string!!!
}
}
Requires
Author: Serverless-appsync
Source Code: https://github.com/serverless-appsync/serverless-appsync-simulator
License: MIT License
1648972740
Generis
Versatile Go code generator.
Generis is a lightweight code preprocessor adding the following features to the Go language :
package main;
// -- IMPORTS
import (
"html"
"io"
"log"
"net/http"
"net/url"
"strconv"
);
// -- DEFINITIONS
#define DebugMode
#as true
// ~~
#define HttpPort
#as 8080
// ~~
#define WriteLine( {{text}} )
#as log.Println( {{text}} )
// ~~
#define local {{variable}} : {{type}};
#as var {{variable}} {{type}};
// ~~
#define DeclareStack( {{type}}, {{name}} )
#as
// -- TYPES
type {{name}}Stack struct
{
ElementArray []{{type}};
}
// -- INQUIRIES
func ( stack * {{name}}Stack ) IsEmpty(
) bool
{
return len( stack.ElementArray ) == 0;
}
// -- OPERATIONS
func ( stack * {{name}}Stack ) Push(
element {{type}}
)
{
stack.ElementArray = append( stack.ElementArray, element );
}
// ~~
func ( stack * {{name}}Stack ) Pop(
) {{type}}
{
local
element : {{type}};
element = stack.ElementArray[ len( stack.ElementArray ) - 1 ];
stack.ElementArray = stack.ElementArray[ : len( stack.ElementArray ) - 1 ];
return element;
}
#end
// ~~
#define DeclareStack( {{type}} )
#as DeclareStack( {{type}}, {{type:PascalCase}} )
// -- TYPES
DeclareStack( string )
DeclareStack( int32 )
// -- FUNCTIONS
func HandleRootPage(
response_writer http.ResponseWriter,
request * http.Request
)
{
local
boolean : bool;
local
natural : uint;
local
integer : int;
local
real : float64;
local
escaped_html_text,
escaped_url_text,
text : string;
local
integer_stack : Int32Stack;
boolean = true;
natural = 10;
integer = 20;
real = 30.0;
text = "text";
escaped_url_text = "&escaped text?";
escaped_html_text = "<escaped text/>";
integer_stack.Push( 10 );
integer_stack.Push( 20 );
integer_stack.Push( 30 );
#write response_writer
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><%= request.URL.Path %></title>
</head>
<body>
<% if ( boolean ) { %>
<%= "URL : " + request.URL.Path %>
<br/>
<%@ natural %>
<%# integer %>
<%& real %>
<br/>
<%~ text %>
<%^ escaped_url_text %>
<%= escaped_html_text %>
<%= "<%% ignored %%>" %>
<%% ignored %%>
<% } %>
<br/>
Stack :
<br/>
<% for !integer_stack.IsEmpty() { %>
<%# integer_stack.Pop() %>
<% } %>
</body>
</html>
#end
}
// ~~
func main()
{
http.HandleFunc( "/", HandleRootPage );
#if DebugMode
WriteLine( "Listening on http://localhost:HttpPort" );
#end
log.Fatal(
http.ListenAndServe( ":HttpPort", nil )
);
}
Constants and generic code can be defined with the following syntax :
#define old code
#as new code
#define old code
#as
new
code
#end
#define
old
code
#as new code
#define
old
code
#as
new
code
#end
The #define
directive can contain one or several parameters :
{{variable name}} : hierarchical code (with properly matching brackets and parentheses)
{{variable name#}} : statement code (hierarchical code without semicolon)
{{variable name$}} : plain code
{{variable name:boolean expression}} : conditional hierarchical code
{{variable name#:boolean expression}} : conditional statement code
{{variable name$:boolean expression}} : conditional plain code
They can have a boolean expression to require they match specific conditions :
HasText text
HasPrefix prefix
HasSuffix suffix
HasIdentifier text
false
true
!expression
expression && expression
expression || expression
( expression )
The #define
directive must not start or end with a parameter.
The #as
directive can use the value of the #define
parameters :
{{variable name}}
{{variable name:filter function}}
{{variable name:filter function:filter function:...}}
Their value can be changed through one or several filter functions :
LowerCase
UpperCase
MinorCase
MajorCase
SnakeCase
PascalCase
CamelCase
RemoveComments
RemoveBlanks
PackStrings
PackIdentifiers
ReplacePrefix old_prefix new_prefix
ReplaceSuffix old_suffix new_suffix
ReplaceText old_text new_text
ReplaceIdentifier old_identifier new_identifier
AddPrefix prefix
AddSuffix suffix
RemovePrefix prefix
RemoveSuffix suffix
RemoveText text
RemoveIdentifier identifier
Conditional code can be defined with the following syntax :
#if boolean expression
#if boolean expression
...
#else
...
#end
#else
#if boolean expression
...
#else
...
#end
#end
The boolean expression can use the following operators :
false
true
!expression
expression && expression
expression || expression
( expression )
Templated HTML code can be sent to a stream writer using the following syntax :
#write writer expression
<% code %>
<%@ natural expression %>
<%# integer expression %>
<%& real expression %>
<%~ text expression %>
<%= escaped text expression %>
<%! removed content %>
<%% ignored tags %%>
#end
--join
option requires to end the statements with a semicolon.#writer
directive is only available for the Go language.Install the DMD 2 compiler (using the MinGW setup option on Windows).
Build the executable with the following command line :
dmd -m64 generis.d
generis [options]
--prefix # : set the command prefix
--parse INPUT_FOLDER/ : parse the definitions of the Generis files in the input folder
--process INPUT_FOLDER/ OUTPUT_FOLDER/ : reads the Generis files in the input folder and writes the processed files in the output folder
--trim : trim the HTML templates
--join : join the split statements
--create : create the output folders if needed
--watch : watch the Generis files for modifications
--pause 500 : time to wait before checking the Generis files again
--tabulation 4 : set the tabulation space count
--extension .go : generate files with this extension
generis --process GS/ GO/
Reads the Generis files in the GS/
folder and writes Go files in the GO/
folder.
generis --process GS/ GO/ --create
Reads the Generis files in the GS/
folder and writes Go files in the GO/
folder, creating the output folders if needed.
generis --process GS/ GO/ --create --watch
Reads the Generis files in the GS/
folder and writes Go files in the GO/
folder, creating the output folders if needed and watching the Generis files for modifications.
generis --process GS/ GO/ --trim --join --create --watch
Reads the Generis files in the GS/
folder and writes Go files in the GO/
folder, trimming the HTML templates, joining the split statements, creating the output folders if needed and watching the Generis files for modifications.
2.0
Author: Senselogic
Source Code: https://github.com/senselogic/GENERIS
License: View license
1646739263
En este tutorial, aprenderemos cómo crear e implementar un backend de aplicación de administración de imágenes.
Podrá almacenar un registro de una imagen en la base de datos, recuperar el registro de la imagen de la base de datos, actualizar el registro e incluso eliminar el registro por completo, según sea el caso.
Para lograr todo esto, usaremos Express (un marco Node.js), Postgres (una base de datos), Cloudinary (un almacenamiento de imágenes basado en la nube), GitHub (para el control/almacenamiento de versiones) y Heroku (una plataforma de alojamiento).
Todas estas herramientas son gratuitas. Así que no tienes que preocuparte por cómo pagarlos. Gracias a estos grandes innovadores.
Si es nuevo en la mayoría de estas tecnologías, le aconsejo que siga mi otro tutorial sobre cómo crear un servidor y cargar imágenes en Cloudinary .
Si está totalmente interesado en Postgres, consulte este tutorial .
Cuando estés listo, ¡manos a la obra!
Por lo tanto, querrá comenzar clonando este proyecto si aún no lo tiene.
En tu pgAdmin :
tutorial
tutorial
tutorial
. (No olvides darle todos los privilegios.)De vuelta en el directorio de su proyecto, instale los paquetes node-postgres ( npm i pg
) y make-runnable ( npm i make-runnable
).
En su package.json
archivo, reemplace el contenido de "scripts"
con "create": "node ./services/dbConnect createTables"
. Usaremos esto para ejecutar el dbConnect
archivo que estamos a punto de crear.
Cree un services/dbConnect
archivo que contenga el siguiente código:
const pg = require("pg");
const config = {
user: "tutorial",
database: "tutorial",
password: "tutorial",
port: 5432,
max: 10, // max number of clients in the pool
idleTimeoutMillis: 30000,
};
const pool = new pg.Pool(config);
pool.on("connect", () => {
console.log("connected to the Database");
});
const createTables = () => {
const imageTable = `CREATE TABLE IF NOT EXISTS
images(
id SERIAL PRIMARY KEY,
title VARCHAR(128) NOT NULL,
cloudinary_id VARCHAR(128) NOT NULL,
image_url VARCHAR(128) NOT NULL
)`;
pool
.query(imageTable)
.then((res) => {
console.log(res);
pool.end();
})
.catch((err) => {
console.log(err);
pool.end();
});
};
pool.on("remove", () => {
console.log("client removed");
process.exit(0);
});
//export pool and createTables to be accessible from anywhere within the application
module.exports = {
createTables,
pool,
};
require("make-runnable");
Ahora estamos listos para crear la tabla en nuestra base de datos. Si estás listo, ¡vamos al rock and roll!
Ejecuta el siguiente código en tu terminal:
npm run create
Si la imagen a continuación es su resultado, entonces está listo para comenzar:
Verifique su pgAdmin , y debería tener su mesa asentada correctamente en su base de datos como en la imagen a continuación:
Muy bien, ha sido un largo camino. Es hora de unir Node, Postgres y Cloudinary.
Primero, requiere el dbConnect.js
archivo en la parte superior del app.js
archivo así:
const db = require('services/dbConnect.js');
Luego, en el app.js
archivo, cree un nuevo punto final (imagen persistente) con el siguiente código:
// persist image
app.post("/persist-image", (request, response) => {
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image,
}
// upload image here
cloudinary.uploader.upload(data.image)
.then().catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
})
Reemplace el then
bloque con el siguiente código:
.then((image) => {
db.pool.connect((err, client) => {
// inset query to run if the upload to cloudinary is successful
const insertQuery = 'INSERT INTO images (title, cloudinary_id, image_url)
VALUES($1,$2,$3) RETURNING *';
const values = [data.title, image.public_id, image.secure_url];
})
})
Los image.public_id
y image.secure_url
se obtienen como parte de los detalles devueltos para una imagen después de que la imagen se haya cargado correctamente en Cloudinary.
Ahora mantenemos un registro de image.public_id
y image.secure_url
(como puede ver en el código anterior) para usarlo para recuperar, actualizar o eliminar el registro de la imagen cuando lo creamos conveniente.
Muy bien, ¡sigamos adelante!
Aún en el then
bloque, agregue el siguiente código debajo de la consulta que creamos:
// execute query
client.query(insertQuery, values)
.then((result) => {
result = result.rows[0];
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Uploaded Successfully",
title: result.title,
cloudinary_id: result.cloudinary_id,
image_url: result.image_url,
},
})
}).catch((e) => {
response.status(500).send({
message: "failure",
e,
});
})
Así que nuestro persist-image
punto final ahora se ve así:
// persist image
app.post("/persist-image", (request, response) => {
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image
}
// upload image here
cloudinary.uploader.upload(data.image)
.then((image) => {
db.pool.connect((err, client) => {
// inset query to run if the upload to cloudinary is successful
const insertQuery = 'INSERT INTO images (title, cloudinary_id, image_url)
VALUES($1,$2,$3) RETURNING *';
const values = [data.title, image.public_id, image.secure_url];
// execute query
client.query(insertQuery, values)
.then((result) => {
result = result.rows[0];
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Uploaded Successfully",
title: result.title,
cloudinary_id: result.cloudinary_id,
image_url: result.image_url,
},
})
}).catch((e) => {
response.status(500).send({
message: "failure",
e,
});
})
})
}).catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
});
Ahora vamos a probar todo nuestro arduo trabajo:
Abra su cartero y pruebe su punto final como se muestra en la imagen a continuación. El mío fue un éxito. ¿Espero que el tuyo tampoco haya tenido errores?
Abra su consola/panel de Cloudinary y verifique su media Library
. Su nueva imagen debería estar sentada allí cómodamente como la mía a continuación:
Y ahora a la razón principal por la que estamos aquí: verifique la images
tabla en su pgAdmin . La mía es la que ves a continuación:
¡Oh, la la! Llegamos hasta aquí. Por favor, tómese un descanso si lo necesita. Estaré aquí esperándote cuando regreses. :)
Si está listo, recuperemos la imagen que conservamos hace un momento.
Comience con este código:
app.get("/retrieve-image/:cloudinary_id", (request, response) => {
});
A continuación, necesitaremos recopilar una identificación única del usuario para recuperar una imagen en particular. Así que agregue const { id } = request.params;
al código anterior así:
app.get("/retrieve-image/:cloudinary_id", (request, response) => {
// data from user
const { cloudinary_id } = request.params;
});
Agregue el siguiente código justo debajo del código anterior:
db.pool.connect((err, client) => {
// query to find image
const query = "SELECT * FROM images WHERE cloudinary_id = $1";
const value = [cloudinary_id];
});
Debajo de la consulta, ejecute la consulta con el siguiente código:
// execute query
client
.query(query, value)
.then((output) => {
response.status(200).send({
status: "success",
data: {
id: output.rows[0].cloudinary_id,
title: output.rows[0].title,
url: output.rows[0].image_url,
},
});
})
.catch((error) => {
response.status(401).send({
status: "failure",
data: {
message: "could not retrieve record!",
error,
},
});
});
Ahora nuestra retrieve-image
API se ve así:
app.get("/retrieve-image/:cloudinary_id", (request, response) => {
// data from user
const { cloudinary_id } = request.params;
db.pool.connect((err, client) => {
// query to find image
const query = "SELECT * FROM images WHERE cloudinary_id = $1";
const value = [cloudinary_id];
// execute query
client
.query(query, value)
.then((output) => {
response.status(200).send({
status: "success",
data: {
id: output.rows[0].cloudinary_id,
title: output.rows[0].title,
url: output.rows[0].image_url,
},
});
})
.catch((error) => {
response.status(401).send({
status: "failure",
data: {
message: "could not retrieve record!",
error,
},
});
});
});
});
A ver qué tan bien lo hicimos:
En su cartero, copie cloudinary_id
y agréguelo a la URL como en la imagen a continuación:
¡SÍ SÍ! También podemos recuperar nuestra imagen.
Si estás aquí, te mereces un aplauso y una ovación de pie por tu laboriosidad.
¡Felicidades! Acabas de alcanzar un gran hito.
El código para almacenar y recuperar registros de imágenes está aquí .
Ahora veremos cómo eliminar y actualizar un registro de imagen según sea el caso. Comencemos con el punto final de eliminación.
En el archivo app.js, comience con el siguiente código:
// delete image
app.delete("delete-image/:cloudinary_id", (request, response) => {
});
A continuación, queremos obtener el ID único de la imagen que queremos eliminar de la URL, es decir, cloudinary_id
. Entonces, dentro del código anterior, agregue:
const { cloudinary_id } = request.params;
Ahora comenzamos el proceso de eliminación.
Primero, eliminamos de Cloudinary. Agregue el siguiente código para eliminar la imagen de Cloudinary:
cloudinary.uploader
.destroy(cloudinary_id)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "Failure",
error,
});
});
En este punto, nuestra API solo puede eliminar la imagen de Cloudinary (puede consultarla en cartero). Pero también queremos deshacernos del registro que tenemos en nuestra base de datos de Postgres.
En segundo lugar, eliminamos de nuestra base de datos de Postgres. Para hacerlo, reemplace el código en el then
bloque con lo siguiente query
:
db.pool.connect((err, client) => {
// delete query
const deleteQuery = "DELETE FROM images WHERE cloudinary_id = $1";
const deleteValue = [cloudinary_id];
})
Ejecute la consulta con el siguiente código debajo:
// execute delete query
client.query(deleteQuery, deleteValue)
.then((deleteResult) => {
response.status(200).send({
message: "Image Deleted Successfully!",
deleteResult
});
}).catch((e) => {
response.status(500).send({
message: "Image Couldn't be Deleted!",
e
});
});
Así que nuestro Endpoint debería verse así:
// delete image
app.delete("/delete-image/:cloudinary_id", (request, response) => {
// unique ID
const { cloudinary_id } = request.params;
// delete image from cloudinary first
cloudinary.uploader
.destroy(cloudinary_id)
// delete image record from postgres also
.then(() => {
db.pool.connect((err, client) => {
// delete query
const deleteQuery = "DELETE FROM images WHERE cloudinary_id = $1";
const deleteValue = [cloudinary_id];
// execute delete query
client
.query(deleteQuery, deleteValue)
.then((deleteResult) => {
response.status(200).send({
message: "Image Deleted Successfully!",
deleteResult,
});
})
.catch((e) => {
response.status(500).send({
message: "Image Couldn't be Deleted!",
e,
});
});
})
})
.catch((error) => {
response.status(500).send({
message: "Failure",
error,
});
});
});
Ha llegado el momento de que pongamos a prueba nuestro Endpoint.
El siguiente es mi Cloudinary media library
con dos imágenes que ya subí. Tome nota de su identificación única ( public_id
).
Si aún no lo tiene, utilice el extremo de imagen persistente para cargar algunas imágenes.
Ahora procedamos al cartero:
Observe, la identificación única, ya que coincide con una de las imágenes en mi biblioteca de medios de Cloudinary.
Desde la salida, ejecutamos el comando DELETE y eso eliminó una FILA de nuestra TABLA de imágenes en nuestra base de datos.
Ahora esta es mi biblioteca de medios con una de las imágenes restantes:
Walahhhh... Ahora podemos deshacernos de una imagen.
Tómese un descanso si lo necesita. ✌🏾
Si está listo, estoy listo para actualizar las imágenes.
Debajo de la delete-image
API, comencemos a crear la update-image
API con el siguiente código:
// update image
app.put("/update-image/:cloudinary_id", (request, response) => {
});
All codes will live in there.
Recopile la identificación única de Cloudinary y los detalles de la nueva imagen del usuario con el siguiente código:
// unique ID
const { cloudinary_id } = request.params;
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image,
};
Elimina la imagen de Cloudinary con el siguiente código:
// delete image from cloudinary first
cloudinary.uploader
.destroy(cloudinary_id)
// upload image here
.then()
.catch((error) => {
response.status(500).send({
message: "failed",
error,
});
});
A continuación, cargue otra imagen en Cloudinary. Para hacer eso, ingrese el siguiente código en el then
bloque:
() => {
cloudinary.uploader
.upload(data.image)
.then()
.catch((err) => {
response.status(500).send({
message: "failed",
err,
});
});
}
Ahora reemplacemos nuestro registro inicial con los nuevos detalles de la imagen. Reemplace el contenido del then
bloque con lo siguiente:
(result) => {
db.pool.connect((err, client) => {
// update query
const updateQuery =
"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4";
const value = [
data.title,
result.public_id,
result.secure_url,
cloudinary_id,
];
});
}
Ejecutamos la consulta usando el siguiente código justo debajo de la declaración de la consulta:
// execute query
client
.query(updateQuery, value)
.then(() => {
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Updated Successfully"
},
});
})
.catch((e) => {
response.status(500).send({
message: "Update Failed",
e,
});
});
En este punto, esto es lo que tengo:
// update image
app.put("/update-image/:cloudinary_id", (request, response) => {
// unique ID
const { cloudinary_id } = request.params;
// collected image from a user
const data = {
title: request.body.title,
image: request.body.image,
};
// delete image from cloudinary first
cloudinary.uploader
.destroy(cloudinary_id)
// upload image here
.then(() => {
cloudinary.uploader
.upload(data.image)
// update the database here
.then((result) => {
db.pool.connect((err, client) => {
// update query
const updateQuery =
"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4";
const value = [
data.title,
result.public_id,
result.secure_url,
cloudinary_id,
];
// execute query
client
.query(updateQuery, value)
.then(() => {
// send success response
response.status(201).send({
status: "success",
data: {
message: "Image Updated Successfully"
},
});
})
.catch((e) => {
response.status(500).send({
message: "Update Failed",
e,
});
});
});
})
.catch((err) => {
response.status(500).send({
message: "failed",
err,
});
});
})
.catch((error) => {
response.status(500).send({
message: "failed",
error,
});
});
});
¡Es tiempo de prueba!
Este es mi cartero en la imagen de abajo:
Tome nota de la ID única de Cloudinary que coincide con la imagen que queda en mi biblioteca de medios de Cloudinary.
Ahora eche un vistazo a mi biblioteca de medios Cloudinary en la imagen que sigue:
Tome nota de la nueva imagen que reemplaza a la inicial en mi biblioteca de medios arriba.
Además, vea que la ID única de Cloudinary coincida con la de mi base de datos con el nuevo título. Ver imagen a continuación:
¡Yayeh! ¡Lo hiciste increíblemente bien! 💪
Acabamos de completar una aplicación de gestión de imágenes con Node.js, Cloudinary y Postgres.
Express Routing nos permite optimizar nuestro código Node.js o darle una estructura más modular al separar la lógica comercial de los controladores. Queremos usar eso para limpiar nuestro código hasta ahora.
Comenzaremos creando una nueva carpeta con el nombre routes
en el directorio raíz:
mk dir routes
En la carpeta de rutas, crea un archivo con el nombre: routes.js
.
Para ventanas:
echo . > routes.js
Para Mac:
touch routes.js
Vacíe el routes.js
archivo si hay algo e ingrese el siguiente código:
const express = require('express');
const router = express.Router();
module.exports = router;
Agrega el siguiente código arriba de la última línea:
const cloudinary = require("cloudinary").v2;
require("dotenv").config();
const db = require("../services/dbConnect.js");
// cloudinary configuration
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
De vuelta en el archivo App.js, elimine el siguiente código:
const cloudinary = require("cloudinary").v2;
require("dotenv").config();
const db = require("./services/dbConnect.js");
// cloudinary configuration
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
Mueva todas las API a routes.js
.
Cambie todas las apariciones de app
a con router
cuidado.
Mi routes.js
archivo ahora se ve así .
De vuelta en el app.js
archivo, importe el routes.js
archivo así:
// import the routes file
const routes = require("./routes/routes")
Ahora registre las rutas así:
// register the routes
app.use('/', routes);
Este es mi app.js
archivo en este momento:
const express = require("express");
const app = express();
// import the routes file
const routes = require("./routes/routes")
// body parser configuration
const bodyParser = require("body-parser");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// register the routes
app.use('/', routes);
module.exports = app;
Es hora de probar y ver si nuestras rutas siguen funcionando como antes.
Asegúrate de que el tuyo funcione como el mío a continuación:
¡Guau! Hemos podido separar nuestras rutas de nuestro app.js
archivo.
El código para esto está aquí .
Aunque nuestro routes.js
archivo todavía es largo, tenemos una buena base para separar nuestra lógica comercial de nuestros controladores. Ha llegado el momento de hacer precisamente eso.
Comience creando una nueva carpeta en la routes
carpeta y asígnele el nombre controllers
.
En la carpeta de controladores, cree 5 archivos y asígneles el nombre de los 5 puntos finales.
Nuestra carpeta y archivos deben estar estructurados de la siguiente manera:
Volviendo al archivo route.js, trabajemos en la image-upload
API. Cortar el siguiente código:
(request, response) => {
// collected image from a user
const data = {
image: request.body.image,
};
// upload image here
cloudinary.uploader
.upload(data.image)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
}
En el imageUpload
archivo, equipare el código que ya cortó desde el image-upload
punto final para que le exports.imageUpload
guste:
exports.imageUpload = (request, response) => {
// collected image from a user
const data = {
image: request.body.image,
};
// upload image here
cloudinary.uploader
.upload(data.image)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
}
Ahora importemos lo necesario para que este código funcione. Así que este es mi imageUpload
archivo en este momento:
const cloudinary = require("cloudinary").v2;
require("dotenv").config();
// cloudinary configuration
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET,
});
exports.imageUpload = (request, response) => {
// collected image from a user
const data = {
image: request.body.image,
};
// upload image here
cloudinary.uploader
.upload(data.image)
.then((result) => {
response.status(200).send({
message: "success",
result,
});
})
.catch((error) => {
response.status(500).send({
message: "failure",
error,
});
});
}
Importemos y registremos la imageUpload
API en el routes.js
archivo así:
const imageUpload = require("./controllers/imageUpload");
// image upload API
router.post("image-upload", imageUpload.imageUpload);
Ahora tenemos esta línea de código que apunta a la imageUpload
API en el imageUpload.js
archivo del routes.js
archivo.
¡Qué asombroso! Nuestro código es más legible.
Asegúrese de probar la API para asegurarse de que funciona correctamente. El mío funciona perfectamente. Ver imagen a continuación:
¡Ahora es tu turno!
Aplique lo que ha aprendido a las otras API. Veamos lo que tienes.
Te estaré esperando del otro lado...
Si estás aquí, entonces creo que has hecho el tuyo y están funcionando perfectamente, o al menos, ya diste lo mejor de ti. ¡Prestigio!
Mira el mío aquí .
Felicidades. Usted es maravilloso :)
El código de optimización de código está aquí .
Muy bien, al siguiente paso.
Ahora que hemos completado nuestra aplicación, implementémosla en Heroku para que podamos acceder a ella incluso sin estar en nuestra computadora portátil donde se escribió el código.
Lo guiaré a través de la carga de nuestra aplicación en GitHub y su implementación en Heroku .
Sin más preámbulos, ensuciémonos las manos.
Subir o subir a GitHub es tan fácil como comer tu comida favorita. Consulte este recurso para aprender a enviar su proyecto desde su máquina local a GitHub.
Comencemos por crear una cuenta en Heroku .
Si ha creado una cuenta, es posible que se le haya pedido que cree una aplicación (es decir, una carpeta donde se alojará su aplicación). Puedes hacer eso, pero yo haré el mío usando mi terminal ya que el terminal viene con algunas funcionalidades adicionales que necesitaremos más adelante.
Abra su proyecto en una terminal si aún no lo ha hecho. Usaré el terminal integrado VS Code.
Instale la CLI de Heroku:
npm install heroku
Inicie sesión en la CLI de Heroku. Esto abrirá una ventana del navegador, que puede usar para iniciar sesión.
heroku login
Crea una aplicación. Puede tener cualquier nombre. estoy node-postgres-cloudinary
usando
heroku create node-postgres-cloudinary
Vaya a su tablero de Heroku y encontrará la aplicación recién creada.
Waalaah!
Así es como se ve el mío en la imagen de arriba. Ya tengo algunas aplicaciones allí, pero puedes ver la que acabo de crear.
Ahora agreguemos la base de datos PostgreSQL a la aplicación.
Haga clic en la aplicación que acaba de crear. Te llevará al tablero de la aplicación.
Haga clic en la Resources
pestaña/menú.
En la Add-ons
Sección, busque y seleccione Heroku Postgres
.
Asegúrese de seleccionar el Hobby Dev - Free
plan en la ventana emergente que aparece a continuación:
Haga clic en el provision
botón para agregarlo a la aplicación de la siguiente manera:
Haga clic en Heroku Postgres
para ir al Heroku Postgres
tablero.
Haga clic en la settings
pestaña:
Haga clic en View Credentials
:
En las Credenciales, nos interesa la CLI de Heroku. Lo usaremos en un momento.
De vuelta a la terminal.
Confirmemos si Heroku Postgres
se agregó con éxito. Introduce lo siguiente en la terminal:
heroku addons
¡Síiiiiiiiii! Se agregó con éxito.
Antes de continuar, asegúrese de que su PostgreSQL path
esté configurado correctamente si está en Windows . Siga este enlace para aprender a configurar un archivo path
. La ruta debe ser así: C:\Program Files\PostgreSQL\<VERSION>\bin
.
La versión dependerá de la instalada en su máquina. El mío es: C:\Program Files\PostgreSQL\12\bin
ya que estoy usando el version 12
.
La siguiente imagen podría ser útil:
Es posible que deba navegar a la carpeta donde está instalado PostgreSQL en su máquina para encontrar su propia ruta.
Inicie sesión en el Heroku Postgres
uso de la CLI de Heroku desde nuestras Heroku Postgres
credenciales. Este es el mío, el tuyo será diferente:
heroku pg:psql postgresql-slippery-19135 --app node-postgres-cloudinary
Si recibió un error, lo más probable es que su ruta no esté configurada correctamente.
Por el momento, mi base de datos se ve así:
const pg = require("pg");
const config = {
user: "tutorial",
database: "tutorial",
password: "tutorial",
port: 5432,
max: 10, // max number of clients in the pool
idleTimeoutMillis: 30000,
};
const pool = new pg.Pool(config);
pool.on("connect", () => {
console.log("connected to the Database");
});
Si intenta conectar Heroku a esto, obtendrá un error. Esto se debe a que Heroku ya tiene una connection string
configuración. Así que tenemos que configurar nuestra conexión para que Heroku pueda conectarse fácilmente.
Voy a refactorizar mi archivo de conexión a la base de datos ( dbConnect.js
) y .env
el archivo para que esto suceda.
const pg = require('pg');
require('dotenv').config();
// set production variable. This will be called when deployed to a live host
const isProduction = process.env.NODE_ENV === 'production';
// configuration details
const connectionString = `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_DATABASE}`;
// if project has been deployed, connect with the host's DATABASE_URL
// else connect with the local DATABASE_URL
const pool = new pg.Pool({
connectionString: isProduction ? process.env.DATABASE_URL : connectionString,
ssl: isProduction,
});
// display message on success if successful
pool.on('connect', () => {
console.log('Teamwork Database connected successfully!');
});
DB_USER="tutorial"
DB_PASSWORD="tutorial"
DB_HOST="localhost"
DB_PORT="5432"
DB_DATABASE="tutorial"
Con la configuración del archivo dbconnect
y .env
, estamos listos para exportar nuestra base de datos y tablas desde nuestra máquina local a heroku postgres
.
Vaya a su pgAdmin
y localice la base de datos para este tutorial. El mío es un tutorial.
Haga clic derecho sobre él y seleccione Backup
. Esto abrirá una nueva ventana.
Ingrese un nombre para el archivo SQL como lo hice yo. Seleccione el plain
formato. Luego haga clic en Copia de seguridad. Esto guardará el archivo en su carpeta de documentos.
Localice el archivo y muévalo al directorio del proyecto. Puede estar en cualquier lugar del directorio, pero elijo mover el mío al services
directorio porque contiene los archivos relacionados con la base de datos.
De vuelta en la terminal, navegue a la carpeta que contiene el archivo SQL y ejecute el siguiente código para agregar las tablas que acabamos de exportar a la base de heroku postgres
datos:
cat <your-SQL-file> | <heroku-CLI-from-the-heroku-posgres-credentials>
Así es como se ve el mío:
cat tutorial.sql | heroku pg:psql postgresql-slippery-19135 --app node-postgres-cloudinary
¿Notaste que cambié el directorio a servicios ( cd services
)? Ahí es donde se encuentra mi sql
archivo.
¡Guau! Acabamos de exportar con éxito nuestra base de datos y tablas a Heroku.
ya casi termina...
Agregue los archivos en los que hemos realizado cambios:
$ git add .
El punto ( .
) agrega todos los archivos.
Confirme sus últimos cambios:
$ git commit -m "refactored the dbConnect and .env file to fit in to heroku; Added the database SQL file"
Empuje los archivos confirmados:
$ git push origin -u master
Ve al tablero de tu aplicación:
Seleccione el método de implementación de GitHub:
Busque y seleccione un repositorio, y haga clic en connect
:
Seleccione la rama que desea implementar (en mi caso, es la master
rama):
Habilite la implementación automática haciendo clic en el Enable automatic deployment
botón como en la imagen de arriba.
Haga clic en el Deploy
botón en el despliegue manual
No tendremos que hacer todo esto para implementaciones posteriores.
Ahora tiene un botón que le indica "ver el sitio" después de completar la compilación. Pinchalo. Esto abrirá su aplicación en una nueva pestaña.
¡Oh, no! ¿Un insecto? ¿¿Error de la aplicación??
No te preocupes, es solo un pequeño problema. Algo que nunca debe olvidar hacer al realizar implementaciones. La mayoría de los servicios de alojamiento lo requerirán.
Regrese al directorio raíz de su proyecto.
Cree un archivo y asígnele un nombre Procfile
(no tiene extensión).
En el archivo, ingrese el siguiente código:
web: node index.js
Esto dirige a Heroku al archivo del servidor ( index.js
), que es el punto de entrada de la aplicación. Si su servidor está en un archivo diferente, modifíquelo según sea necesario.
Guarde el archivo y envíe los nuevos cambios a GitHub.
Espere de 2 a 5 minutos para que Heroku detecte automáticamente los cambios en su repositorio de GitHub y represente los cambios en la aplicación.
Ahora puede actualizar esa página de error y ver que su arduo trabajo da sus frutos:
También puede probar la retrieve image
ruta y verla funcionando.
¡Felicidades! Qué hazaña has logrado.
Otras rutas (persistir-imagen, actualizar-imagen y eliminar-imagen) no funcionarán porque no hemos aprovisionado o agregado cloudinary
complementos. Es tan sencillo como el PostgreSQL
que acabamos de hacer. Así que puedes darle una oportunidad.
Comenzamos este tutorial con el objetivo de aprender cómo construir una aplicación de backend usando Express, Postgres, Cloudinary, Github y Heroku.
Aprendimos cómo almacenar, recuperar, eliminar y actualizar un registro de imagen. Luego organizamos nuestro código con Express Routing, lo subimos a GitHub y lo implementamos en Heroku. Eso fue mucho.
Espero que estén de acuerdo en que valió la pena porque aprendimos mucho. Debe intentar agregar el complemento Cloudinary usted mismo para mejorar aún más su conocimiento.
¡Gracias por leer!
Enlace: https://www.freecodecamp.org/news/how-to-build-a-backend-application/
1648079400
This is a general purpose OpenAPI code generator. It is currently used to completely generate the HTTP code in the Java SDK, and generate some of the HTTP code in our Golang SDK.
We currently have two HTTP endpoints. One for algod and one for indexer, so in most cases, this tool would be run once with each OpenAPI spec.
~$ mvn package -DskipTests
~$ java -jar target/generator-*-jar-with-dependencies.jar -h
You'll see that there are a number of subcommands:
The command line interface uses JCommander to define the command line interface. See Main.java.
The main code involves an OpenAPI parser / event generator and several listeners for the actual generation.
The template subcommand is using Apache Velocity as the underlying template engine. Things like variables, loops, and statements are all supported. So business logic can technically be implemented in the template if it's actually necessary.
There are three phases: client, query, and model. Each phase must provide two templates, one for the file generation and one to specify the filename to be used. If all results should go to the same file. For query and model generation the template will be executed once for each query / model. If you want to put everything in one file return the same filename twice in a row and the processing will exit early.
phase | filename | purpose |
---|---|---|
client | client.vm | Client class with functions to call each query. |
client | client_filename.vm | File to write to the client output directory. |
query | query.vm | Template to use for generating query files. |
query | query_filename.vm | File to write to the query output directory. |
model | model.vm | Template to use for generating model files. |
model | model_filename.vm | File to write to the model output directory. |
The template command will only run the templates which have an output directory is provided. So if you just want to regenerate models, only use the -m option.
-c, --clientOutputDir
Directory to write client file(s).
-m, --modelsOutputDir
Directory to write model file(s).
-q, --queryOutputDir
Directory to write query file(s).
The template subcommand accepts a --propertyFiles option. It can be provided multiple times, or as a comma separated list of files. Property files will be processed and bound to a velocity variable available to templates.
For details on a type you can put it directly into your template. It will be serialized along with its fields for your reference. Here is a high level description of what is available:
template | variable | type | purpose |
---|---|---|---|
all | str | StringHelpers.java | Some string utilities are available. See StringHelpers.java for details. There are simple things like $str.capitalize("someData") -> SomeData , and also some more complex helpers like $str.formatDoc($query.doc, "// ") which will split the document at the word boundary nearest to 80 characters without going over, and add a prefix to each new line. |
all | order | OrderHelpers.java | Some ordering utilities available. See OrderHelpers.java for details. An example utility function is $order.propertiesWithOrdering($props, $preferred_order) , where $props is a list of properties and $preferred_order is a string list to use when ordering the properties list. |
all | propFile | Properties | The contents of all property files are available with this variable. For example if package=com.algorand.v2.algod is in the property file, the template may use ${propFile.package} . |
all | models | HashMap<StructDef, List<TypeDef>> | A list of all models. |
all | queries | List<QueryDef> | A list of all queries. |
query | q | QueryDef | The current query definition. |
model | def | StructDef | The current model definition if multiple files are being generated. |
model | props | List<TypeDef> | A list of properties for the current model. |
In the following example, we are careful to generate the algod code first because the algod models are a strict subset of the indexer models. For that reason, we are able to reuse some overlapping models from indexer in algod.
~$ java -jar generator*jar template
-s algod.oas2.json
-t go_templates
-c algodClient
-m allModels
-q algodQueries
-p common_config.properties,algod_config.properties
~$ java -jar generator*jar template
-s indexer.oas2.json
-t go_templates
-c indexerClient
-m allModels
-q indexerQueries
-p common_config.properties,indexer_config.properties
There is a test template that gives you some basic usage in the test_templates directory.
You can generate the test code in the output directory with the following commands:
~$ mkdir output
~$ java -jar target/generator-*-jar-with-dependencies.jar \
template \
-s /path/to/a/spec/file/indexer.oas2.json \
-t test_templates/ \
-m output \
-q output \
-c output \
-p test_templates/my.properties
The Golang templates are in the go_templates directory.
The Golang HTTP API is only partially generated. The hand written parts were not totally consistent with the spec and that makes it difficult to regenerate them. Regardless, an attempt has been made. In the templates there are some macros which map "generated" values to the hand written ones. For example the query types have this mapping:
#macro ( queryType )
#if ( ${str.capitalize($q.name)} == "SearchForAccounts" )
SearchAccounts## The hand written client doesn't quite match the spec...
#elseif ( ${str.capitalize($q.name)} == "GetStatus" )
Status##
#elseif ( ${str.capitalize($q.name)} == "GetPendingTransactionsByAddress" )
PendingTransactionInformationByAddress##
#elseif ( ${str.capitalize($q.name)} == "GetPendingTransactions" )
PendingTransactions##
#else
${str.capitalize($q.name)}##
#end
#end
Other mappings are more specific to the language, such as the OpenAPI type to SDK type:
#macro ( toQueryType $param )##
#if ( $param.algorandFormat == "RFC3339 String" )
string##
#elseif ( $param.type == "integer" )
uint64##
#elseif ( $param.type == "string" )
string##
#elseif ( $param.type == "boolean" )
bool##
#elseif( $param.type == "binary" )
string##
#else
UNHANDLED TYPE
- ref: $!param.refType
- type: $!param.type
- array type: $!param.arrayType
- algorand format: $!param.algorandFormat
- format: $!param.format
##$unknown.type ## force a template failure because $unknown.type does not exist.
#end
#end
Because of this, we are phasing in code generation gradually by skipping some types. The skipped types are specified in the property files:
common_config.properties
model_skip=AccountParticipation,AssetParams,RawBlockJson,etc,...
algod_config.properties
query_skip=Block,BlockRaw,SendRawTransaction,SuggestedParams,etc,...
indexer_config.properties
query_skip=LookupAssetByID,LookupAccountTransactions,SearchForAssets,LookupAssetBalances,LookupAssetTransactions,LookupBlock,LookupTransactions,SearchForTransactions
The Java templates are in the java_templates directory.
These are not used yet, they are the initial experiments for the template engine. Since the Java SDK has used code generation from the beginning, we should be able to fully migrate to the template engine eventually.
In general, the automation pipeline will build and run whatever Dockerfile
is found in a repository's templates
directory. For instructions on how to configure the templates
directory, look at the repository template directory example.
If you are trying to verify that automatic code generation works as intended, we recommend creating a testing branch from that repository and using the SKIP_PR=true
environment variable to avoid creating pull requests. If all goes according to plan, generated files should be available in the container's /repo
directory.
The automatic generator scripts depend on certain prerequisites that are listed in automation/REQUIREMENTS.md. Once those conditions have been satisfied, automatically generating code for external repositories should be as easy as building and running a particular SDK's templates/Dockerfile
file.
Download Details:
Author: algorand
Source Code: https://github.com/algorand/generator
License:
#algorand #blockchain #cryptocurrency #java #golang #openapi
1602569524
E-scooters are becoming more and more familiar. The compactness, coupled with the skill of evading jam-packed traffics, makes the fast-paced world lean towards this micro-mobility innovation. Besides, with COVID-19 propelling the need for safety and privacy, you do not have drivers in an E-scooters ecosystem! With the system being entirely automated, people can smart-lock and unlock E-scooters without any hassle.
Various top manufacturers are spending quality hours exhaustively on their R&D to shift from fuel-led automobiles to electric power-generating vehicles. Although people hesitate to make investments when it comes to buying an e-vehicle, using such vehicles for commuting is no big deal. If you’re an entrepreneur aiming to launch an Uber for E-Scooters app, now is the time to roll up your sleeves as E-scooters are being legalized in numerous countries, including New York.
Now, let’s discuss the remunerative advantages of E-scooters and why entrepreneurs needn’t hesitate to initiate their E-scooter App development.
Lucrative Benefits of E-Scooters
Outplay traffic effortlessly: One of the main concerns of people worldwide is not reaching the destination on time due to prolonged traffic. With four-wheelers becoming more predominant, the situation is steeping towards the worsening phase. With its compact nature, E-scooters can help people sail past traffic without a sweat. This way, people conserve and utilize their time efficiently.
The environmental impact: As simple as it may sound, automobiles pollute the environment on a massive scale. It is high-time people raise their concerns against environmental degradation. E-scooters are the best alternatives from the environmental perspective. These scooters run on a 500W electric motor, eliminating any form of pollution.
Inexpensive in every aspect: The maintenance and fuel costs of automobiles is way too high as vehicles get older. However, with an E-scooter, all it takes is a rechargeable battery with less or no maintenance at all. Moreover, entrepreneurs get to enhance their profits seamlessly, even after providing economical rides to passengers. There’s only an initial investment cost that an entrepreneur needs to take care of.
The 5-Step Workflow of an E-Scooters App
While building a smartphone application, it is essential to focus on the platform’s workflow. An E-scooter app with a user-friendly architecture and immersive workflow can create an instant impact among the audience. Let’s discuss the simple yet intuitive 5-step workflow here,
Users register with the platform and locate E-scooters nearby by enabling their location preferences.
Users choose their best-suited E-scooters based on numerous metrics like pricing, battery capacity, ratings, etc.
Users unlock the vehicle by scanning the QR code. They initiate their trip and drive towards their destination.
Upon reaching the destination, users park the E-scooters securely and smart-lock the vehicle.
The app displays the total fare with a detailed breakdown. Users pay the amount via a multitude of payment gateways and share their experience in the form of ratings & reviews.
Features that make the E-Scooter app stand apart
Apps like Lime, Bird, etc., have already set a benchmark when it comes to the E-Scooter app market. You need USPs to lure customer attention. Some of the unique elements worth-considering include,
QR scanning - To initiate and terminate rides.
In-app wallet - To pay for rides effortlessly.
Multi-lingual support - To access the app in the customers’ preferred language.
Schedule bookings - To book rides well-in-advance.
In-app chat/call - To establish a connection between the support team and users.
VoIP-based Call masking - To mask users’ contact details.
Geofencing - To map virtual boundaries and keep an eye on E-scooters.
Capitalize on the growing market
Establishing your E-Scooters Rental app at the spur of the moment is highly essential if you wish to scale your business in the shortest possible time. Some of the reasons to initiate your app development right away include,
The unexplored market: The E-Scooter market is still in its nascent stages. Rolling out an app with the right feature-set and approach can help you yield unrestricted revenue.
Competitors are experiencing massive growth: Apps like Lime, Bird, etc., witness unprecedented growth in the past few years. Lime was valued at $2.4 billion in 2019. On the other hand, Bird has spread across 100 cities in Europe. With competitors reaping profits, it is high-time entrepreneurs needn’t hesitate to invest in this business opportunity.
The ‘E’ shift among customers: People are gradually moving towards e-vehicles as a measure to conserve time and environment. By rolling out an on-demand app for E-scooters that is economical, people will inevitably turn towards your platform for the daily commute.
Conclusion
In this modern world, saving time and energy is the need of the hour. Add to that the indispensable role of conserving the environment. E-scooters cater to all these aspects comprehensively. Make the most out of the situation and have no second thoughts about initiating your E-Scooter app development.
#uber for e-scooters #e-scooter app development #e-scooter app #e-scooter rental app #uber like app for e-scooters #uber for e-scooters app