Acortador de links, ejemplo de como diseñar la base de datos básica y muestra del proyecto en una aplicación real.


Los acortadores de enlaces sirven para simplificar la manera en que compartimos y gestionamos los enlaces en línea. En el siguiente artículo, verás la estructura básica de la base de datos para poder desarrollar un proyecto de este tipo y el proyecto implementado en NextJS con ayuda de Supabase para la autenticación y la base de datos. Cabe aclarar que Supabase simplifica el desarrollo de aplicaciones al proporcionar una infraestructura de backend completa y fácil de usar.


Estructura de la base de datos

Para este proyecto se utilizará una estructura básica de base de datos para simplicar el desarrollo de la aplicación. Tendremos solo 3 tablas: users, links y visits_links.

¿Que se guardará en cada tabla?

Diseño entidad relación

Cabe resaltar que links tendrá de llave foránea el id de users y visits_links tendrá de llave foránea el id de la tabla link.


Puntos clave de la aplicación

De manera general verás los puntos claves de la aplicación para que tenga el funcionamiento que queremos. Tu puedes organizar las rutas de la aplicación como quieras. Todo estará de manera resumida en este artículo, para ver el código completo de la aplicación haz click aquí.

Nos saltaremos toda la conexión con Supabase y como hacer autenticación con github, estos pasos los puedes conseguir en la documentación de Supabase.

Crear cliente de Supabase en el servidor

export function createClient () {
  const cookieStore = cookies()
 
  // El tipo Database se puede obtener gracias a un comando de Supabase.
  // Para más información consulta la documentación de Supabase
 
  return createServerClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get (name: string) {
          return cookieStore.get(name)?.value
        }
      }
    }
  )
}

Crear cliente de Supabase en el cliente

'use client'
 
export function createClient () {
  return createBrowserClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

Como guardar los links

Para guardar los links utilizamos los server actions de NextJS.

export const FormCreateLink = () => {
  const addLink = async(formData: FormData) => {
    'use server'
 
    // obtenemos el name y url del formulario 
    const name = formData.get('name')?.toString().replace(' ', '-')
    const url = formData.get('url')?.toString()
 
    // Verificamos si la url comienza con el protocolo http
    if (!url.startsWith('http')) {
      throw new Error('URL invalida') 
    }
 
    // Verificamos si el name no exite 
    const supabase = createClient()
    const { data, error: errorSelect } = await supabase.from('links').select('*').eq('name', name)
 
    if (errorSelect) {
      throw new Error('Error al guardar el enlace') 
    }
 
    if (data.length !== 0) {
      throw new Error('El nombre ya existe') 
    }
 
    // Guardamos el link
    const { data: { user } } = await supabase.auth.getUser()
    const shortUrl = `https://dominio.ejemplo/${name}`
    const { error } = await supabase.from('links').insert({ name, url, short_url: shortUrl, uid_user: user?.id })
 
    if (error) {
      throw new Error('Error al guardar el enlace') 
    }
  }
 
  return (
    <form action={addLink}>
      <input type='text' name='name' placeholder='Nombre del enlace' required />
      <input type='text' name='url' placeholder='URL' required />
      <button>Duardae enlace</button>
    </form>
  )
}

Como editar los links

Recuerda que para ver, guardar, actualizar y actualizar debes editar las políticas de la base de datos de Supabase.

interface Props {
  link: TypeLink
}
 
export const FormEditLink: FC<Props> = ({ link }) => {
 
  const editLink = async(formData: FormData) => {
    'use server'
 
    // obtenemos el name del formulario 
    const name = formData.get('name')?.toString().replace(' ', '-')
 
    // Verificamos si el name no exite 
    const supabase = createClient()
    const { data, error: errorSelect } = await supabase.from('links').select('*').eq('name', name)
 
    if (errorSelect) {
      throw new Error('Error al guardar el enlace') 
    }
 
    if (data.length !== 0) {
      throw new Error('El nombre ya existe') 
    }
 
    // Guardamos el link editado
    const shortUrl = `https://dominio.ejemplo/${name}`
    const { error } = await supabase
      .from('links')
      .update({ name, short_url: shortUrl })
      .eq('id', id)
 
    if (error) {
      throw new Error('Error al guardar el enlace') 
    }
  }
 
  return (
    <form action={editLink}>
      <input type='text' name='name' placeholder='Nombre del enlace' />
      <button>Editar enlace</button>
    </form>
  )
}

Como borrar los links

Para borrar un link podríamos utilizar un botón con la siguiente funcionalidad.

export const DeleteButton = () => {
  const handleDelete = async () => {
    const { error } = await supabase.from('links').delete().eq('id',  link.id)
    if (error) {
      throw new Error('Error al eliminar el link')
    }
  } 
 
  return (
    <button type='button' onClick={handleDelete}>
      // icono de la caneca de basura 
      <TrashIcon  />
    </button>
  )
} 

Redireccionamiento a la URL original

Este es el punto clave de la aplicación. Vamos a crear la ruta /[name], esta nos va a servir para obtener el nombre del link, buscar el enlace por dicho nombre en la base de datos y redirigir a la URL original.

Por ejemplo, un usuario x da click en el enlace https://dominio.ejemplo/rubertweb, la aplicacion extrae el nombre de la URL rubertweb, obtiene el link que coincida con el nombre dado de la base de datos y redirige a la URL original que en este caso sería https://rubertweb.dev.

Recordemos que el link que se obtiene de la tabla links tienen las propiedades nombre, url personalizada o corta, url original, id del usuario que la creó y la fecha de creación.

El código del archivo /[name]/page.tsx quedaria de la siguiente manera:

interface Props {
  params: {
    name: string
  }
}
 
const RedirectingPage: FC<Props> = async ({ params: { name } }) => {
  const supabase = createClient()
  const { data: links, error } = await supabase.from('links').select('*').eq('name', decodeURIComponent(name))
 
  if (error != null || links == null || links?.length === 0) {
    return redirect('/')
  }
 
  return redirect(links[0].url)
}
 
export default RedirectingPage

Como guardar los clicks de los enlaces personalizados

Para guadar el click hecho en un enlace vamos a agregar una funcionalidad al componente anterior.

interface Props {
  params: {
    name: string
  }
}
 
const RedirectingPage: FC<Props> = async ({ params: { name } }) => {
  const supabase = createClient()
  const { data: links, error } = await supabase.from('links').select('*').eq('name', decodeURIComponent(name))
 
  if (error != null || links == null || links?.length === 0) {
    return redirect('/')
  }
 
  // La siguiente linea inserta en la tabla visits_links un nuevo registro con la id del link. 
  // La base de datos agrega id y fecha de creacion automáticamente 
  await supabase.from('visits_links').insert({ uid_link: links[0].id })
  return redirect(links[0].url)
}
 
export default RedirectingPage

Con estos componentes tendrás las funcionalidades básicas de la aplicación. Recuerda que puedes ver el código completo del ejemplo en mi repositorio de Github. Tambien me puedes contactar para mas información en mi página de contacto ❤️.