1 de septiembre de 2020

Creando un CRUD en Tiempo Real con Firebase y Laravel

En este post quiero hablar de algo que desde hace un tiempo vengo escuchando y que no es nuevo pero que nunca había tenido tiempo de probar. Hablo de Firebase.

Firebase es una solución de Google que nos provee de herramientas tanto para aplicaciones móviles (el fin para el que está pensado) como para aplicaciones web. No voy a explicar que es Firebase porque ya ellos lo explican de forma clara y sencilla en este enlace.

Sin entretenerme mucho más, lo que voy a hacer en este breve tutorial es un CRUD con Laravel (mi principal framework de desarrollo) en su versión 7.25, la última hasta la fecha, que se actualizará en tiempo real usando Firebase como base de datos.

Por si queda alguna duda, CRUD significa:

  • Create / Crear
  • Read / Leer
  • Update / Actualizar
  • Delete / Borrar

¡Vamos al lío!

Gracias a Google Firebase podemos desarrollar muchas funcionalidades en tiempo real sin necesidad de implementar complicados códigos. El resultado de este breve tutorial será el que ves a continuación:

Pero vamos por partes, el primer paso será crear nuestra aplicación en Firebase, el proceso es muy sencillo.

1.- Creando la aplicación en Firebase

Para crear un nuevo proyecto en Firebase deberemos de registrarnos con nuestra cuenta de google. No voy a explicar el proceso porque es un registro más como otro cualquiera.

Una vez dentro de la consola de Firebase pulsaremos sobre la opción: "Añadir Proyecto"

A continuación, el sistema nos guiará solicitándonos los datos de la aplicación:

  • Paso 1: Nombre del proyecto, en mi caso le pondré solo CrudLaravel
  • Paso 2: Google Analytics. Para medir las visitas y usos del proyecto, en este caso lo desmarcaré, pero en todas mis proyectos en producción lo marco para controlar analíticas, es realmente útil.

Ahora pulsamos en "Crear Proyecto" y tras unos segundos ya tendremos disponible el panel de control de nuestra aplicación.

En el panel de nuestro proyecto tendremos una imagen similar a esta:

Si pulsamos sobre el círculo blanco que tiene el símbolo </> que significa "Web" empezaremos a configurar todo lo necesario para integrar Firebase con Laravel.

El sistema nos preguntará por el Apodo de la aplicación: Es un nombre privado, yo siempre uso com.vayesa.nombre, en este caso sería "com.vayesa.crudlaravel" es importante que sepas que esto no lo va a ver nadie excepto tu.

Después de crear el proyecto en Firebase, obtendrás los siguientes datos que serán los que usemos en Laravel para interactuar con la base de datos de nuestro proyecto:

Antes de empezar con Laravel, deremos de habilitar la base de datos en Firebase. Esto antes era automático pero ahora tenemos que hacerlo expresamente para que funcione como queremos.

Es sencillo, en la consola de Firebase, en el sidebar de la izquierda, buscamos "Realtime Database". Una vez dentro veremos una página de presentación que nos pondrá "Crear Base de Datos". La creamos y ya podemos pasar a Laravel:

Iniciamos en modo bloqueo pero una vez dentro de la configuración de Realtime Database cambiamos las "Rules" a true, tanto ".read" como ".write".

2.- Creando el proyecto en Laravel

Para crear un proyecto en Laravel, deberemos de tener instalado composer en nuestro equipo de trabajo. Si no sabes como crear un nuevo proyecto Laravel o no tienes composer instalado, puedes ver como hacerlo aquí.

Una vez instalado todo lo necesario para crear un proyecto en Laravel, en nuestra consola ejecutaremos:

composer create-project --prefer-dist laravel/laravel crudlaravel

Esto descargará la última versión disponible de Laravel (En estos momentos la 7.25) y lo preparará para que empecemos a trabajar en él.

Cuando el proceso termine, empezaremos a configurar nuestro proyecto con todo lo necesario para trabajar con Firebase.

Añadiendo soporte Firebase a Laravel

Para que Laravel pueda empezar a trabajar con Firebase, deberemos de modificar el fichero "config/services.php" añadiendo el siguiente código:

'firebase' => [
    'api_key' => 'api_key',
    'auth_domain' => 'auth_domain',
    'database_url' => 'database_url',
    'project_id' => 'project_id',
    'storage_bucket' => 'storage_bucket',
    'messaging_sender_id' => 'messaging_sender_id',
    'app_id' => 'app_id',
    'measurement_id' => 'measurement_id',
],

Generando la ruta donde llamaremos al CRUD

Si estás familizarizado con Laravel, sabrás que para cualquier tipo de acceso necesitas declarar una ruta, por eso, vamos a crear una ruta nueva en "routes/web.php" con el siguiente codigo:

Route::get('users', 'HomeController@users');

Creando el controlador "HomeController"

En la ruta hacemos referencia a un controlador que por ahora no existe, así que si intentamos consumir la ruta tendremos un bonito error de Laravel diciéndonos que no encuentra el controlador y es obvio, pero a veces se olvida este paso.

Para crear el controlador ejecutaremos la siguiente línea de comando:

php artisan make:controller HomeController

Una vez generado el nuevo controlador, vamos a editarlo para pedirle que nos devuelva una vista cuando le solicitemos el método "users":

<?php
 
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class HomeController extends Controller
{
    public function users() // Método que se llama desde la ruta.
    {
        return view('userslist'); // Nos devuelve una vista
    }
}
?>

Creando la vista "userslist"

Ya casi estamos terminando los preparativos para entrar en materia y comunicarnos con Firebase. En esta ocasión tenemos que crear una vista llamada "userslist.blade.php" dentro de "resources/views".

Una vez creada la vista, ya si que sí nos ponemos a escribir código del bueno.

3.- Javascript para comunicarnos con Firebase

Esto lo puedes hacer directamente dentro de la vista, pero personalmente prefiero crear un js externo para tener un código más limpio. Para ello crearé un fichero que se llamará "fb_crud.js" y lo meteré en "public/js"

Vamos por partes:

Obtención de datos (READ)

// Obtención de datos - Read
	firebase.database().ref('users/').on('value', function(snapshot) {
		var value = snapshot.val();
		var htmls = [];
		$.each(value, function(index, value){
			
			if(value) {

			htmls.push('<tr>\
			<td>'+ value.first_name +'</td>\
			<td>'+ value.last_name +'</td>\
			<td><a data-toggle="modal" data-target="#update-modal" class="btn btn-outline-success updateData" data-id="'+index+'">Editar</a>\
			<a data-toggle="modal" data-target="#remove-modal" class="btn btn-outline-danger removeData" data-id="'+index+'">Eliminar</a></td>\
			</tr>');
			}    

			lastIndex = index;
		});
		$('#tbody').html(htmls);
		$("#submitUser").removeClass('disabled');
	});

Creación de datos (Create)

$('#submitUser').on('click', function(){
	var values = $("#addUser").serializeArray();
	var first_name = values[0].value;
	var last_name = values[1].value;
	var userID = lastIndex+1;
	 
	firebase.database().ref('users/' + userID).set({
		first_name: first_name,
		last_name: last_name,
	});
	 
	// Reassign lastID value
	lastIndex = userID;
	$("#addUser input").val("");
});

Actualización de datos (Update)

// Actualización de datos - Update
var updateID = 0;
$('body').on('click', '.updateData', function() {
	updateID = $(this).attr('data-id');
	firebase.database().ref('users/' + updateID).on('value', function(snapshot) {
		var values = snapshot.val();
		var updateData = '<div class="form-group">\
		<label for="first_name" class="col-md-12 col-form-label">Nombre</label>\
		<div class="col-md-12">\
		<input id="first_name" type="text" class="form-control" name="first_name" value="'+values.first_name+'" required autofocus>\
	</div>\
</div>\
<div class="form-group">\
	<label for="last_name" class="col-md-12 col-form-label">Apellidos</label>\
	<div class="col-md-12">\
	<input id="last_name" type="text" class="form-control" name="last_name" value="'+values.last_name+'" required autofocus>\
	</div>\
</div>';
		 
	$('#updateBody').html(updateData);
	});
});
 
$('.updateUserRecord').on('click', function() {
	var values = $(".users-update-record-model").serializeArray();
	var postData = {
	first_name : values[0].value,
	last_name : values[1].value,
};
 
var updates = {};
updates['/users/' + updateID] = postData;
 
firebase.database().ref().update(updates);
 
$("#update-modal").modal('hide');
});

Borrado de datos (Delete)

// Borrado de datos - Delete
$("body").on('click', '.removeData', function() {
	var id = $(this).attr('data-id');
        $('body').find('.users-remove-record-model').append('<input name="id" type="hidden" value="'+ id +'">');
});
 
$('.deleteMatchRecord').on('click', function(){
	var values = $(".users-remove-record-model").serializeArray();
	var id = values[0].value;
	firebase.database().ref('users/' + id).remove();
	$('body').find('.users-remove-record-model').find( "input" ).remove();
	$("#remove-modal").modal('hide');
	});

$('.remove-data-from-delete-form').click(function() {
	$('body').find('.users-remove-record-model').find( "input" ).remove();
});

4.- Diseñando la vista e implementando el CRUD

Antes de empezar este último punto quiero que sepas que no he usado las buenas prácticas ni los recursos de que nos brinda Laravel para implementar bootstrap y jquery añadiéndolo diréctamente desde los CDN de código y no con Laravel Mix como debería, pero lo importante de este tutorial no es aprender Laravel sino como conectarnos e introducir datos en Firebase en tiempo real.

Vista "userslist.blade.php"

<!doctype html>
<html lang="en">
<head>
    <title>Laravel + Firebase - CRUD por Vayesa.com</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
    <script src="https://www.gstatic.com/firebasejs/4.9.1/firebase.js"></script>

</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="card card-default">
                <div class="card-header">
                    <div class="row">
                        <div class="col-md-10">
                            <strong>Nuevo usuario</strong>
                        </div>
                    </div>
                </div>
                <div class="card-body">
                    <form id="addUser" class="" method="POST" action="">
                        <div class="form-group">
                            <label for="first_name" class="col-md-12 col-form-label">Nombre</label>

                            <div class="col-md-12">
                                <input id="first_name" type="text" class="form-control" name="first_name" value="" required autofocus>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="last_name" class="col-md-12 col-form-label">Apellidos</label>

                            <div class="col-md-12">
                                <input id="last_name" type="text" class="form-control" name="last_name" value="" required autofocus>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-md-12 col-md-offset-3">
                                <button type="button" class="btn btn-primary btn-block desabled" id="submitUser">
                                    Crear
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
        <div class="col-md-8">
            <div class="card card-default">
                <div class="card-header">
                    <div class="row">
                        <div class="col-md-10">
                            <strong>Usuarios</strong>
                        </div>
                    </div>
                </div>

                <div class="card-body">
                    <table class="table table-bordered">
                        <tr>
                            <th>Nombre</th>
                            <th>Apellidosme</th>
                            <th width="180" class="text-center">Acción</th>
                        </tr>
                        <tbody id="tbody">
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Delete Modal -->
<form action="" method="POST" class="users-remove-record-model">
    <div id="remove-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="custom-width-modalLabel" aria-hidden="true" style="display: none;">
        <div class="modal-dialog" style="width:55%;">
            <div class="modal-content">
                <div class="modal-header">
                    <h4 class="modal-title" id="custom-width-modalLabel">Borrar usuario</h4>
                    <button type="button" class="close remove-data-from-delete-form" data-dismiss="modal" aria-hidden="true">×</button>
                </div>
                <div class="modal-body">
                    <h4>¿Seguro que desea borrar el usuario?</h4>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default waves-effect remove-data-from-delete-form" data-dismiss="modal">Cerrar</button>
                    <button type="button" class="btn btn-danger waves-effect waves-light deleteMatchRecord">Borrar</button>
                </div>
            </div>
        </div>
    </div>
</form>

<!-- Update Model -->
<form action="" method="POST" class="users-update-record-model form-horizontal">
    <div id="update-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="custom-width-modalLabel" aria-hidden="true" style="display: none;">
        <div class="modal-dialog" style="width:55%;">
            <div class="modal-content" style="overflow: hidden;">
                <div class="modal-header">
                    <h4 class="modal-title" id="custom-width-modalLabel">Actualizar usuario</h4>
                    <button type="button" class="close update-data-from-delete-form" data-dismiss="modal" aria-hidden="true">×</button>
                </div>
                <div class="modal-body" id="updateBody">
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default waves-effect update-data-from-delete-form" data-dismiss="modal">Cerrar</button>
                    <button type="button" class="btn btn-success waves-effect waves-light updateUserRecord">Actualizar</button>
                </div>
            </div>
        </div>
    </div>
</form>
<script>
    var config = {
        apiKey: "{{ config('services.firebase.api_key') }}",
authDomain: "{{ config('services.firebase.auth_domain') }}",
databaseURL: "{{ config('services.firebase.database_url') }}",
projectId: "{{ config('services.firebase.project_id') }}",
storageBucket: "{{ config('services.firebase.storage_bucket') }}",
messagingSenderId: "{{ config('services.firebase.messaging_sender_id') }}",
appId: "{{ config('services.firebase.app_id') }}"
    };

    firebase.initializeApp(config);
    var database = firebase.database();
    var lastIndex = 0;
</script>
<script src="./js/fb_crud.js"></script>
</body>
</html>

Estoy preparando este mismo tutorial en vídeo para hacerlo mucho más sencillo y que podáis seguirlo, pero de esta forma tienes el código directamente para copiarlo y pegarlo.

Espero que te haya servido y nos seguimos leyendo en el próximo tutorial que publicaré que será para consumir los registros de esta base de datos con una aplicación móvil desarrollada en flutter.

¡Hasta la próxima!

¿Quieres que trabajemos juntos?
¿HABLAMOS DE TU IDEA?
Todos los derechos reservados.

VAYESA
× ¿Puedo ayudarte?
linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram