Overview
SIGEAC communicates with a Laravel backend API using Axios. All API interactions are configured with authentication, error handling, and proper TypeScript types.
Axios Configuration
The Axios instance is configured in lib/axios.ts:
import axios from 'axios'
import Cookies from 'js-cookie'
const axiosInstance = axios . create ({
baseURL: process . env . NEXT_PUBLIC_API_BASE_URL ,
withCredentials: true ,
headers: {
"skip_zrok_interstitial" : true ,
},
})
// Request interceptor - Add auth token
axiosInstance . interceptors . request . use (( config ) => {
const token = Cookies . get ( 'auth_token' )
if ( token ) {
const authHeader = token . startsWith ( 'Bearer ' ) ? token : `Bearer ${ token } `
config . headers . Authorization = authHeader
}
return config
})
// Response interceptor - Handle 401 errors
axiosInstance . interceptors . response . use (
( response ) => response ,
( error ) => {
if ( error . response && error . response . status === 401 ) {
console . warn ( "⚠️ Sesión inválida: Redirigiendo al login..." )
Cookies . remove ( 'auth_token' )
Cookies . remove ( 'jwt' )
if ( typeof window !== 'undefined' ) {
window . location . href = '/login?session=expired'
}
}
return Promise . reject ( error )
}
)
export default axiosInstance
See: lib/axios.ts:1
Environment Configuration
Set the API base URL in .env.local:
NEXT_PUBLIC_API_BASE_URL = http://localhost:8000/api
# Or production URL:
# NEXT_PUBLIC_API_BASE_URL=https://api.sigeac.com/api
The NEXT_PUBLIC_ prefix exposes the variable to the browser. Only use it for public URLs.
API Patterns
GET Requests
List Resources
Single Resource
With Query Parameters
Fetch a collection: const fetchClients = async ( company : string ) : Promise < Client []> => {
const { data } = await axiosInstance . get ( `/ ${ company } /clients` )
return data
}
Fetch one item: const fetchClient = async ( company : string , id : string ) : Promise < Client > => {
const { data } = await axiosInstance . get ( `/ ${ company } /clients/ ${ id } ` )
return data
}
Add filters and pagination: const fetchClients = async (
company : string ,
params : { page ?: number ; search ?: string ; status ?: string }
) : Promise < PaginatedResponse < Client >> => {
const { data } = await axiosInstance . get ( `/ ${ company } /clients` , {
params
})
return data
}
POST Requests
Create Resource
With FormData
Custom Action
Create a new item: const createClient = async (
company : string ,
clientData : CreateClientData
) : Promise < Client > => {
const { data } = await axiosInstance . post (
`/ ${ company } /clients` ,
clientData
)
return data
}
Upload files: const uploadDocument = async (
company : string ,
file : File ,
metadata : object
) => {
const formData = new FormData ()
formData . append ( 'document' , file )
formData . append ( 'metadata' , JSON . stringify ( metadata ))
const { data } = await axiosInstance . post (
`/ ${ company } /documents` ,
formData ,
{
headers: {
'Content-Type' : 'multipart/form-data' ,
},
}
)
return data
}
Non-RESTful endpoints: const enrollInTraining = async (
company : string ,
trainingId : string ,
employeeId : string
) => {
const { data } = await axiosInstance . post (
`/ ${ company } /trainings/ ${ trainingId } /enroll` ,
{ employee_id: employeeId }
)
return data
}
PATCH/PUT Requests
Partial Update (PATCH)
Full Update (PUT)
Update specific fields: const updateClient = async (
company : string ,
id : string ,
updates : Partial < Client >
) : Promise < Client > => {
const { data } = await axiosInstance . patch (
`/ ${ company } /clients/ ${ id } ` ,
updates
)
return data
}
Replace entire resource: const replaceClient = async (
company : string ,
id : string ,
clientData : CreateClientData
) : Promise < Client > => {
const { data } = await axiosInstance . put (
`/ ${ company } /clients/ ${ id } ` ,
clientData
)
return data
}
DELETE Requests
const deleteClient = async ( company : string , id : string ) : Promise < void > => {
await axiosInstance . delete ( `/ ${ company } /clients/ ${ id } ` )
}
Multi-Tenant URL Structure
SIGEAC uses company slugs in URLs for multi-tenancy:
/{company}/clients # List clients for company
/{company}/clients/{id} # Get specific client
/hangar74/flights # Hangar74's flights
/transmandu/flights # Transmandu's flights
Example:
const { selectedCompany } = useCompanyStore ()
const company = selectedCompany ?. slug // "hangar74" or "transmandu"
const { data } = await axiosInstance . get ( `/ ${ company } /clients` )
Response Types
Single Resource Response
interface Client {
id : number
name : string
email : string
phone : string
created_at : string
updated_at : string
}
// API Response
const response = await axiosInstance . get < Client >( '/clients/1' )
const client : Client = response . data
Collection Response
// Simple array
const response = await axiosInstance . get < Client []>( '/clients' )
const clients : Client [] = response . data
Paginated Response
interface PaginatedResponse < T > {
data : T []
current_page : number
last_page : number
per_page : number
total : number
from : number
to : number
}
const response = await axiosInstance . get < PaginatedResponse < Client >>(
'/clients?page=1'
)
const {
data : clients ,
current_page ,
last_page ,
total
} = response . data
Error Response
interface ApiErrorResponse {
message : string
errors ?: Record < string , string []> // Validation errors
}
try {
await axiosInstance . post ( '/clients' , data )
} catch ( error ) {
if ( axios . isAxiosError ( error )) {
const errorData = error . response ?. data as ApiErrorResponse
console . error ( errorData . message )
console . error ( errorData . errors )
}
}
Error Handling
Global Error Handling
The response interceptor handles 401 errors globally:
axiosInstance . interceptors . response . use (
( response ) => response ,
( error ) => {
if ( error . response ?. status === 401 ) {
// Redirect to login
window . location . href = '/login?session=expired'
}
return Promise . reject ( error )
}
)
Component-Level Error Handling
Query Error
Mutation Error
import { useGetClients } from '@/hooks/general/clientes/useGetClients'
export function ClientList () {
const { data , error , isError } = useGetClients ( company )
if ( isError ) {
return (
< Alert variant = "destructive" >
< AlertTitle > Error </ AlertTitle >
< AlertDescription >
{ error . message || ' Failed to load clients '}
</ AlertDescription >
</ Alert >
)
}
return < div >{ /* Render clients */ } </ div >
}
Validation Errors
Laravel returns validation errors in a specific format:
interface ValidationError {
message : string
errors : {
[ field : string ] : string []
}
}
// Example response:
{
"message" : "The given data was invalid." ,
"errors" : {
"email" : [ "The email field is required." ],
"name" : [ "The name must be at least 3 characters." ]
}
}
Handle in your component:
import { AxiosError } from 'axios'
type ValidationErrorResponse = {
message : string
errors : Record < string , string []>
}
try {
await createClient . mutateAsync ({ company , data })
} catch ( error ) {
if ( axios . isAxiosError ( error )) {
const errorResponse = error . response ?. data as ValidationErrorResponse
if ( errorResponse . errors ) {
// Set form errors
Object . entries ( errorResponse . errors ). forEach (([ field , messages ]) => {
form . setError ( field as any , {
message: messages [ 0 ]
})
})
}
}
}
Authentication Flow
Login
import { useMutation } from '@tanstack/react-query'
import axiosInstance from '@/lib/axios'
import { createCookie } from '@/lib/cookie'
import { createSession } from '@/lib/session'
const loginMutation = useMutation ({
mutationFn : async ( credentials : { login : string ; password : string }) => {
const response = await axiosInstance . post < User >( '/login' , credentials , {
headers: { 'Content-Type' : 'application/json' },
})
const token = response . headers [ 'authorization' ]
if ( ! token ) throw new Error ( 'No se recibió token de autenticación' )
createCookie ( "auth_token" , token )
await createSession ( response . data . id )
return response . data
},
onSuccess : async ( userData ) => {
await fetchUser ()
queryClient . invalidateQueries ({ queryKey: [ 'user' ] })
router . push ( '/inicio' )
toast . success ( '¡Bienvenido!' )
},
onError : ( err : Error ) => {
const axiosError = err as AxiosError < ApiErrorResponse >
const errorMessage = axiosError . response ?. data ?. message || 'Error al iniciar sesión'
setError ( errorMessage )
toast . error ( 'Error' , { description: errorMessage })
},
})
See: contexts/AuthContext.tsx:137
Logout
const logout = async () => {
try {
await axiosInstance . post ( '/logout' )
} catch ( error ) {
console . error ( 'Logout error:' , error )
} finally {
Cookies . remove ( 'auth_token' )
queryClient . clear ()
router . push ( '/login' )
}
}
Get Current User
const fetchUser = async () : Promise < User > => {
const { data } = await axiosInstance . get < User >( '/user' )
return data
}
File Uploads
Single File Upload
const uploadCertificate = async (
company : string ,
articleId : string ,
file : File
) => {
const formData = new FormData ()
formData . append ( 'certificate' , file )
formData . append ( 'article_id' , articleId )
const { data } = await axiosInstance . post (
`/ ${ company } /articles/ ${ articleId } /certificates` ,
formData ,
{
headers: {
'Content-Type' : 'multipart/form-data' ,
},
}
)
return data
}
Multiple Files Upload
const uploadMultipleDocuments = async (
company : string ,
files : File []
) => {
const formData = new FormData ()
files . forEach (( file , index ) => {
formData . append ( `documents[ ${ index } ]` , file )
})
const { data } = await axiosInstance . post (
`/ ${ company } /documents/batch` ,
formData ,
{
headers: {
'Content-Type' : 'multipart/form-data' ,
},
}
)
return data
}
With Progress Tracking
import { useState } from 'react'
const [ uploadProgress , setUploadProgress ] = useState ( 0 )
const uploadWithProgress = async ( file : File ) => {
const formData = new FormData ()
formData . append ( 'file' , file )
const { data } = await axiosInstance . post ( '/upload' , formData , {
onUploadProgress : ( progressEvent ) => {
const percentCompleted = Math . round (
( progressEvent . loaded * 100 ) / ( progressEvent . total || 1 )
)
setUploadProgress ( percentCompleted )
},
})
return data
}
Download Files
Download as Blob
const downloadCertificate = async (
company : string ,
certificateId : string
) => {
const response = await axiosInstance . get (
`/ ${ company } /certificates/ ${ certificateId } /download` ,
{
responseType: 'blob' ,
}
)
// Create download link
const url = window . URL . createObjectURL ( new Blob ([ response . data ]))
const link = document . createElement ( 'a' )
link . href = url
link . setAttribute ( 'download' , 'certificate.pdf' )
document . body . appendChild ( link )
link . click ()
link . remove ()
}
Get File URL
const getDocumentUrl = ( path : string ) => {
const baseUrl = process . env . NEXT_PUBLIC_API_BASE_URL ?. replace ( '/api' , '' )
return ` ${ baseUrl } /storage/ ${ path } `
}
// Usage
< img src = { getDocumentUrl (article.image)} alt = {article. part_number } />
Request Cancellation
Cancel on Component Unmount
import { useEffect } from 'react'
import axios from 'axios'
useEffect (() => {
const source = axios . CancelToken . source ()
const fetchData = async () => {
try {
const { data } = await axiosInstance . get ( '/data' , {
cancelToken: source . token ,
})
setData ( data )
} catch ( error ) {
if ( axios . isCancel ( error )) {
console . log ( 'Request cancelled' )
}
}
}
fetchData ()
return () => {
source . cancel ( 'Component unmounted' )
}
}, [])
Best Practices
Always use the configured Axios instance
Always define response types: // ✗ Bad
const { data } = await axiosInstance . get ( '/clients' )
// ✓ Good
const { data } = await axiosInstance . get < Client []>( '/clients' )
Handle errors appropriately
Different errors need different handling:
401: Redirect to login (handled globally)
403: Show access denied message
404: Show not found message
422: Show validation errors
500: Show server error message
Use environment variables
Never hardcode API URLs: // ✗ Bad
const baseURL = 'http://localhost:8000/api'
// ✓ Good
const baseURL = process . env . NEXT_PUBLIC_API_BASE_URL
Testing API Integration
Mock Axios in Tests
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
const mock = new MockAdapter ( axiosInstance )
// Mock GET request
mock . onGet ( '/clients' ). reply ( 200 , [
{ id: 1 , name: 'Client 1' },
{ id: 2 , name: 'Client 2' },
])
// Mock POST request
mock . onPost ( '/clients' ). reply ( 201 , {
id: 3 ,
name: 'New Client' ,
})
// Test
const { data } = await axiosInstance . get ( '/clients' )
expect ( data ). toHaveLength ( 2 )
Next Steps
Data Fetching Learn data fetching patterns with React Query
Custom Hooks Create reusable API hooks