├── .gitignore ├── api.http ├── app.js ├── movies.json ├── package-lock.json ├── package.json ├── schemas └── movies.js └── web └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /api.http: -------------------------------------------------------------------------------- 1 | ### Recuperar todas las películas 2 | GET http://localhost:1234/movies 3 | 4 | ### Recuperar una película por id 5 | GET http://localhost:1234/movies/c8a7d63f-3b04-44d3-9d95-8782fd7dcfaf 6 | 7 | ### Recuperar todas las películas por un género 8 | GET http://localhost:1234/movies?genre=ACTION 9 | 10 | ### Crear una película con POST 11 | POST http://localhost:1234/movies 12 | Content-Type: application/json 13 | 14 | { 15 | "sql": "SELECT * FROM users", 16 | "title": "The Godfather", 17 | "year": 1975, 18 | "director": "Francis Ford Coppola", 19 | "duration": 175, 20 | "poster": "https://img.fruugo.com/product/4/49/14441494_max.jpg", 21 | "genre": [ 22 | "Crime", 23 | "Drama" 24 | ] 25 | } 26 | 27 | ### Actualizar una película 28 | PATCH http://localhost:1234/movies/dcdd0fad-a94c-4810-8acc-5f108d3b18c3 29 | Content-Type: application/json 30 | 31 | { 32 | "year": 2022 33 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const crypto = require('node:crypto') 3 | const cors = require('cors') 4 | 5 | const movies = require('./movies.json') 6 | const { validateMovie, validatePartialMovie } = require('./schemas/movies') 7 | 8 | const app = express() 9 | app.use(express.json()) 10 | app.use(cors({ 11 | origin: (origin, callback) => { 12 | const ACCEPTED_ORIGINS = [ 13 | 'http://localhost:8080', 14 | 'http://localhost:1234', 15 | 'https://movies.com', 16 | 'https://midu.dev' 17 | ] 18 | 19 | if (ACCEPTED_ORIGINS.includes(origin)) { 20 | return callback(null, true) 21 | } 22 | 23 | if (!origin) { 24 | return callback(null, true) 25 | } 26 | 27 | return callback(new Error('Not allowed by CORS')) 28 | } 29 | })) 30 | app.disable('x-powered-by') // deshabilitar el header X-Powered-By: Express 31 | 32 | // métodos normales: GET/HEAD/POST 33 | // métodos complejos: PUT/PATCH/DELETE 34 | 35 | // CORS PRE-Flight 36 | // OPTIONS 37 | 38 | // Todos los recursos que sean MOVIES se identifica con /movies 39 | app.get('/movies', (req, res) => { 40 | const { genre } = req.query 41 | if (genre) { 42 | const filteredMovies = movies.filter( 43 | movie => movie.genre.some(g => g.toLowerCase() === genre.toLowerCase()) 44 | ) 45 | return res.json(filteredMovies) 46 | } 47 | res.json(movies) 48 | }) 49 | 50 | app.get('/movies/:id', (req, res) => { 51 | const { id } = req.params 52 | const movie = movies.find(movie => movie.id === id) 53 | if (movie) return res.json(movie) 54 | res.status(404).json({ message: 'Movie not found' }) 55 | }) 56 | 57 | app.post('/movies', (req, res) => { 58 | const result = validateMovie(req.body) 59 | 60 | if (!result.success) { 61 | // 422 Unprocessable Entity 62 | return res.status(400).json({ error: JSON.parse(result.error.message) }) 63 | } 64 | 65 | // en base de datos 66 | const newMovie = { 67 | id: crypto.randomUUID(), // uuid v4 68 | ...result.data 69 | } 70 | 71 | // Esto no sería REST, porque estamos guardando 72 | // el estado de la aplicación en memoria 73 | movies.push(newMovie) 74 | 75 | res.status(201).json(newMovie) 76 | }) 77 | 78 | app.delete('/movies/:id', (req, res) => { 79 | const { id } = req.params 80 | const movieIndex = movies.findIndex(movie => movie.id === id) 81 | 82 | if (movieIndex === -1) { 83 | return res.status(404).json({ message: 'Movie not found' }) 84 | } 85 | 86 | movies.splice(movieIndex, 1) 87 | 88 | return res.json({ message: 'Movie deleted' }) 89 | }) 90 | 91 | app.patch('/movies/:id', (req, res) => { 92 | const result = validatePartialMovie(req.body) 93 | 94 | if (!result.success) { 95 | return res.status(400).json({ error: JSON.parse(result.error.message) }) 96 | } 97 | 98 | const { id } = req.params 99 | const movieIndex = movies.findIndex(movie => movie.id === id) 100 | 101 | if (movieIndex === -1) { 102 | return res.status(404).json({ message: 'Movie not found' }) 103 | } 104 | 105 | const updateMovie = { 106 | ...movies[movieIndex], 107 | ...result.data 108 | } 109 | 110 | movies[movieIndex] = updateMovie 111 | 112 | return res.json(updateMovie) 113 | }) 114 | 115 | const PORT = process.env.PORT ?? 1234 116 | 117 | app.listen(PORT, () => { 118 | console.log(`server listening on port http://localhost:${PORT}`) 119 | }) 120 | -------------------------------------------------------------------------------- /movies.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "dcdd0fad-a94c-4810-8acc-5f108d3b18c3", 3 | "title": "The Shawshank Redemption", 4 | "year": 1994, 5 | "director": "Frank Darabont", 6 | "duration": 142, 7 | "poster": "https://i.ebayimg.com/images/g/4goAAOSwMyBe7hnQ/s-l1200.webp", 8 | "genre": ["Drama"], 9 | "rate": 9.3 10 | }, 11 | { 12 | "id": "c8a7d63f-3b04-44d3-9d95-8782fd7dcfaf", 13 | "title": "The Dark Knight", 14 | "year": 2008, 15 | "director": "Christopher Nolan", 16 | "duration": 152, 17 | "poster": "https://i.ebayimg.com/images/g/yokAAOSw8w1YARbm/s-l1200.jpg", 18 | "genre": ["Action", "Crime", "Drama"], 19 | "rate": 9.0 20 | }, 21 | { 22 | "id": "5ad1a235-0d9c-410a-b32b-220d91689a08", 23 | "title": "Inception", 24 | "year": 2010, 25 | "director": "Christopher Nolan", 26 | "duration": 148, 27 | "poster": "https://m.media-amazon.com/images/I/91Rc8cAmnAL._AC_UF1000,1000_QL80_.jpg", 28 | "genre": ["Action", "Adventure", "Sci-Fi"], 29 | "rate": 8.8 30 | }, 31 | { 32 | "id": "241bf55d-b649-4109-af7c-0e6890ded3fc", 33 | "title": "Pulp Fiction", 34 | "year": 1994, 35 | "director": "Quentin Tarantino", 36 | "duration": 154, 37 | "poster": "https://www.themoviedb.org/t/p/original/vQWk5YBFWF4bZaofAbv0tShwBvQ.jpg", 38 | "genre": ["Crime", "Drama"], 39 | "rate": 8.9 40 | }, 41 | { 42 | "id": "9e6106f0-848b-4810-a11a-3d832a5610f9", 43 | "title": "Forrest Gump", 44 | "year": 1994, 45 | "director": "Robert Zemeckis", 46 | "duration": 142, 47 | "poster": "https://i.ebayimg.com/images/g/qR8AAOSwkvRZzuMD/s-l1600.jpg", 48 | "genre": ["Drama", "Romance"], 49 | "rate": 8.8 50 | }, 51 | { 52 | "id": "7e3fd5ab-60ff-4ae2-92b6-9597f0308d1", 53 | "title": "Gladiator", 54 | "year": 2000, 55 | "director": "Ridley Scott", 56 | "duration": 155, 57 | "poster": "https://img.fruugo.com/product/0/60/14417600_max.jpg", 58 | "genre": ["Action", "Adventure", "Drama"], 59 | "rate": 8.5 60 | }, 61 | { 62 | "id": "c906673b-3948-4402-ac7f-73ac3a9e3105", 63 | "title": "The Matrix", 64 | "year": 1999, 65 | "director": "Lana Wachowski", 66 | "duration": 136, 67 | "poster": "https://i.ebayimg.com/images/g/QFQAAOSwAQpfjaA6/s-l1200.jpg", 68 | "genre": ["Action", "Sci-Fi"], 69 | "rate": 8.7 70 | }, 71 | { 72 | "id": "b6e03689-cccd-478e-8565-d92f40813b13", 73 | "title": "Interstellar", 74 | "year": 2014, 75 | "director": "Christopher Nolan", 76 | "duration": 169, 77 | "poster": "https://m.media-amazon.com/images/I/91obuWzA3XL._AC_UF1000,1000_QL80_.jpg", 78 | "genre": ["Adventure", "Drama", "Sci-Fi"], 79 | "rate": 8.6 80 | }, 81 | { 82 | "id": "aa391090-b938-42eb-b520-86ea0aa3917b", 83 | "title": "The Lord of the Rings: The Return of the King", 84 | "year": 2003, 85 | "director": "Peter Jackson", 86 | "duration": 201, 87 | "poster": "https://i.ebayimg.com/images/g/0hoAAOSwe7peaMLW/s-l1600.jpg", 88 | "genre": ["Action", "Adventure", "Drama"], 89 | "rate": 8.9 90 | }, 91 | { 92 | "id": "2e6900e2-0b48-4fb6-ad48-09c7086e54fe", 93 | "title": "The Lion King", 94 | "year": 1994, 95 | "director": "Roger Allers, Rob Minkoff", 96 | "duration": 88, 97 | "poster": "https://m.media-amazon.com/images/I/81BMmrwSFOL._AC_UF1000,1000_QL80_.jpg", 98 | "genre": ["Animation", "Adventure", "Drama"], 99 | "rate": 8.5 100 | }, 101 | { 102 | "id": "04986507-b3ed-442c-8ae7-4c5df804f896", 103 | "title": "The Avengers", 104 | "year": 2012, 105 | "director": "Joss Whedon", 106 | "duration": 143, 107 | "poster": "https://img.fruugo.com/product/7/41/14532417_max.jpg", 108 | "genre": ["Action", "Adventure", "Sci-Fi"], 109 | "rate": 8.0 110 | }, 111 | { 112 | "id": "7d2832f8-c70a-410e-8963-4c93bf36cc9c", 113 | "title": "Jurassic Park", 114 | "year": 1993, 115 | "director": "Steven Spielberg", 116 | "duration": 127, 117 | "poster": "https://vice-press.com/cdn/shop/products/Jurassic-Park-Editions-poster-florey.jpg?v=1654518755&width=1024", 118 | "genre": ["Adventure", "Sci-Fi"], 119 | "rate": 8.1 120 | }, 121 | { 122 | "id": "ccf36f2e-8566-47f7-912d-9f4647250bc7", 123 | "title": "Titanic", 124 | "year": 1997, 125 | "director": "James Cameron", 126 | "duration": 195, 127 | "poster": "https://i.pinimg.com/originals/42/42/65/4242658e6f1b0d6322a4a93e0383108b.png", 128 | "genre": ["Drama", "Romance"], 129 | "rate": 7.8 130 | }, 131 | { 132 | "id": "8fb17ae1-bdfe-45e5-a871-4772d7e526b8", 133 | "title": "The Social Network", 134 | "year": 2010, 135 | "director": "David Fincher", 136 | "duration": 120, 137 | "poster": "https://i.pinimg.com/originals/7e/37/b9/7e37b994b613e94cba64f307b1983e39.jpg", 138 | "genre": ["Biography", "Drama"], 139 | "rate": 7.7 140 | }, 141 | { 142 | "id": "6a360a18-c645-4b47-9a7b-2a71babbf3e0", 143 | "title": "Avatar", 144 | "year": 2009, 145 | "director": "James Cameron", 146 | "duration": 162, 147 | "poster": "https://i.etsystatic.com/35681979/r/il/dfe3ba/3957859451/il_fullxfull.3957859451_h27r.jpg", 148 | "genre": ["Action", "Adventure", "Fantasy"], 149 | "rate": 7.8 150 | }] -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-api", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "rest-api", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cors": "2.8.5", 13 | "express": "4.18.2", 14 | "zod": "3.21.4" 15 | } 16 | }, 17 | "node_modules/accepts": { 18 | "version": "1.3.8", 19 | "license": "MIT", 20 | "dependencies": { 21 | "mime-types": "~2.1.34", 22 | "negotiator": "0.6.3" 23 | }, 24 | "engines": { 25 | "node": ">= 0.6" 26 | } 27 | }, 28 | "node_modules/array-flatten": { 29 | "version": "1.1.1", 30 | "license": "MIT" 31 | }, 32 | "node_modules/body-parser": { 33 | "version": "1.20.1", 34 | "license": "MIT", 35 | "dependencies": { 36 | "bytes": "3.1.2", 37 | "content-type": "~1.0.4", 38 | "debug": "2.6.9", 39 | "depd": "2.0.0", 40 | "destroy": "1.2.0", 41 | "http-errors": "2.0.0", 42 | "iconv-lite": "0.4.24", 43 | "on-finished": "2.4.1", 44 | "qs": "6.11.0", 45 | "raw-body": "2.5.1", 46 | "type-is": "~1.6.18", 47 | "unpipe": "1.0.0" 48 | }, 49 | "engines": { 50 | "node": ">= 0.8", 51 | "npm": "1.2.8000 || >= 1.4.16" 52 | } 53 | }, 54 | "node_modules/bytes": { 55 | "version": "3.1.2", 56 | "license": "MIT", 57 | "engines": { 58 | "node": ">= 0.8" 59 | } 60 | }, 61 | "node_modules/call-bind": { 62 | "version": "1.0.2", 63 | "license": "MIT", 64 | "dependencies": { 65 | "function-bind": "^1.1.1", 66 | "get-intrinsic": "^1.0.2" 67 | }, 68 | "funding": { 69 | "url": "https://github.com/sponsors/ljharb" 70 | } 71 | }, 72 | "node_modules/content-disposition": { 73 | "version": "0.5.4", 74 | "license": "MIT", 75 | "dependencies": { 76 | "safe-buffer": "5.2.1" 77 | }, 78 | "engines": { 79 | "node": ">= 0.6" 80 | } 81 | }, 82 | "node_modules/content-type": { 83 | "version": "1.0.5", 84 | "license": "MIT", 85 | "engines": { 86 | "node": ">= 0.6" 87 | } 88 | }, 89 | "node_modules/cookie": { 90 | "version": "0.5.0", 91 | "license": "MIT", 92 | "engines": { 93 | "node": ">= 0.6" 94 | } 95 | }, 96 | "node_modules/cookie-signature": { 97 | "version": "1.0.6", 98 | "license": "MIT" 99 | }, 100 | "node_modules/cors": { 101 | "version": "2.8.5", 102 | "license": "MIT", 103 | "dependencies": { 104 | "object-assign": "^4", 105 | "vary": "^1" 106 | }, 107 | "engines": { 108 | "node": ">= 0.10" 109 | } 110 | }, 111 | "node_modules/debug": { 112 | "version": "2.6.9", 113 | "license": "MIT", 114 | "dependencies": { 115 | "ms": "2.0.0" 116 | } 117 | }, 118 | "node_modules/depd": { 119 | "version": "2.0.0", 120 | "license": "MIT", 121 | "engines": { 122 | "node": ">= 0.8" 123 | } 124 | }, 125 | "node_modules/destroy": { 126 | "version": "1.2.0", 127 | "license": "MIT", 128 | "engines": { 129 | "node": ">= 0.8", 130 | "npm": "1.2.8000 || >= 1.4.16" 131 | } 132 | }, 133 | "node_modules/ee-first": { 134 | "version": "1.1.1", 135 | "license": "MIT" 136 | }, 137 | "node_modules/encodeurl": { 138 | "version": "1.0.2", 139 | "license": "MIT", 140 | "engines": { 141 | "node": ">= 0.8" 142 | } 143 | }, 144 | "node_modules/escape-html": { 145 | "version": "1.0.3", 146 | "license": "MIT" 147 | }, 148 | "node_modules/etag": { 149 | "version": "1.8.1", 150 | "license": "MIT", 151 | "engines": { 152 | "node": ">= 0.6" 153 | } 154 | }, 155 | "node_modules/express": { 156 | "version": "4.18.2", 157 | "license": "MIT", 158 | "dependencies": { 159 | "accepts": "~1.3.8", 160 | "array-flatten": "1.1.1", 161 | "body-parser": "1.20.1", 162 | "content-disposition": "0.5.4", 163 | "content-type": "~1.0.4", 164 | "cookie": "0.5.0", 165 | "cookie-signature": "1.0.6", 166 | "debug": "2.6.9", 167 | "depd": "2.0.0", 168 | "encodeurl": "~1.0.2", 169 | "escape-html": "~1.0.3", 170 | "etag": "~1.8.1", 171 | "finalhandler": "1.2.0", 172 | "fresh": "0.5.2", 173 | "http-errors": "2.0.0", 174 | "merge-descriptors": "1.0.1", 175 | "methods": "~1.1.2", 176 | "on-finished": "2.4.1", 177 | "parseurl": "~1.3.3", 178 | "path-to-regexp": "0.1.7", 179 | "proxy-addr": "~2.0.7", 180 | "qs": "6.11.0", 181 | "range-parser": "~1.2.1", 182 | "safe-buffer": "5.2.1", 183 | "send": "0.18.0", 184 | "serve-static": "1.15.0", 185 | "setprototypeof": "1.2.0", 186 | "statuses": "2.0.1", 187 | "type-is": "~1.6.18", 188 | "utils-merge": "1.0.1", 189 | "vary": "~1.1.2" 190 | }, 191 | "engines": { 192 | "node": ">= 0.10.0" 193 | } 194 | }, 195 | "node_modules/finalhandler": { 196 | "version": "1.2.0", 197 | "license": "MIT", 198 | "dependencies": { 199 | "debug": "2.6.9", 200 | "encodeurl": "~1.0.2", 201 | "escape-html": "~1.0.3", 202 | "on-finished": "2.4.1", 203 | "parseurl": "~1.3.3", 204 | "statuses": "2.0.1", 205 | "unpipe": "~1.0.0" 206 | }, 207 | "engines": { 208 | "node": ">= 0.8" 209 | } 210 | }, 211 | "node_modules/forwarded": { 212 | "version": "0.2.0", 213 | "license": "MIT", 214 | "engines": { 215 | "node": ">= 0.6" 216 | } 217 | }, 218 | "node_modules/fresh": { 219 | "version": "0.5.2", 220 | "license": "MIT", 221 | "engines": { 222 | "node": ">= 0.6" 223 | } 224 | }, 225 | "node_modules/function-bind": { 226 | "version": "1.1.1", 227 | "license": "MIT" 228 | }, 229 | "node_modules/get-intrinsic": { 230 | "version": "1.2.1", 231 | "license": "MIT", 232 | "dependencies": { 233 | "function-bind": "^1.1.1", 234 | "has": "^1.0.3", 235 | "has-proto": "^1.0.1", 236 | "has-symbols": "^1.0.3" 237 | }, 238 | "funding": { 239 | "url": "https://github.com/sponsors/ljharb" 240 | } 241 | }, 242 | "node_modules/has": { 243 | "version": "1.0.3", 244 | "license": "MIT", 245 | "dependencies": { 246 | "function-bind": "^1.1.1" 247 | }, 248 | "engines": { 249 | "node": ">= 0.4.0" 250 | } 251 | }, 252 | "node_modules/has-proto": { 253 | "version": "1.0.1", 254 | "license": "MIT", 255 | "engines": { 256 | "node": ">= 0.4" 257 | }, 258 | "funding": { 259 | "url": "https://github.com/sponsors/ljharb" 260 | } 261 | }, 262 | "node_modules/has-symbols": { 263 | "version": "1.0.3", 264 | "license": "MIT", 265 | "engines": { 266 | "node": ">= 0.4" 267 | }, 268 | "funding": { 269 | "url": "https://github.com/sponsors/ljharb" 270 | } 271 | }, 272 | "node_modules/http-errors": { 273 | "version": "2.0.0", 274 | "license": "MIT", 275 | "dependencies": { 276 | "depd": "2.0.0", 277 | "inherits": "2.0.4", 278 | "setprototypeof": "1.2.0", 279 | "statuses": "2.0.1", 280 | "toidentifier": "1.0.1" 281 | }, 282 | "engines": { 283 | "node": ">= 0.8" 284 | } 285 | }, 286 | "node_modules/iconv-lite": { 287 | "version": "0.4.24", 288 | "license": "MIT", 289 | "dependencies": { 290 | "safer-buffer": ">= 2.1.2 < 3" 291 | }, 292 | "engines": { 293 | "node": ">=0.10.0" 294 | } 295 | }, 296 | "node_modules/inherits": { 297 | "version": "2.0.4", 298 | "license": "ISC" 299 | }, 300 | "node_modules/ipaddr.js": { 301 | "version": "1.9.1", 302 | "license": "MIT", 303 | "engines": { 304 | "node": ">= 0.10" 305 | } 306 | }, 307 | "node_modules/media-typer": { 308 | "version": "0.3.0", 309 | "license": "MIT", 310 | "engines": { 311 | "node": ">= 0.6" 312 | } 313 | }, 314 | "node_modules/merge-descriptors": { 315 | "version": "1.0.1", 316 | "license": "MIT" 317 | }, 318 | "node_modules/methods": { 319 | "version": "1.1.2", 320 | "license": "MIT", 321 | "engines": { 322 | "node": ">= 0.6" 323 | } 324 | }, 325 | "node_modules/mime": { 326 | "version": "1.6.0", 327 | "license": "MIT", 328 | "bin": { 329 | "mime": "cli.js" 330 | }, 331 | "engines": { 332 | "node": ">=4" 333 | } 334 | }, 335 | "node_modules/mime-db": { 336 | "version": "1.52.0", 337 | "license": "MIT", 338 | "engines": { 339 | "node": ">= 0.6" 340 | } 341 | }, 342 | "node_modules/mime-types": { 343 | "version": "2.1.35", 344 | "license": "MIT", 345 | "dependencies": { 346 | "mime-db": "1.52.0" 347 | }, 348 | "engines": { 349 | "node": ">= 0.6" 350 | } 351 | }, 352 | "node_modules/ms": { 353 | "version": "2.0.0", 354 | "license": "MIT" 355 | }, 356 | "node_modules/negotiator": { 357 | "version": "0.6.3", 358 | "license": "MIT", 359 | "engines": { 360 | "node": ">= 0.6" 361 | } 362 | }, 363 | "node_modules/object-assign": { 364 | "version": "4.1.1", 365 | "license": "MIT", 366 | "engines": { 367 | "node": ">=0.10.0" 368 | } 369 | }, 370 | "node_modules/object-inspect": { 371 | "version": "1.12.3", 372 | "license": "MIT", 373 | "funding": { 374 | "url": "https://github.com/sponsors/ljharb" 375 | } 376 | }, 377 | "node_modules/on-finished": { 378 | "version": "2.4.1", 379 | "license": "MIT", 380 | "dependencies": { 381 | "ee-first": "1.1.1" 382 | }, 383 | "engines": { 384 | "node": ">= 0.8" 385 | } 386 | }, 387 | "node_modules/parseurl": { 388 | "version": "1.3.3", 389 | "license": "MIT", 390 | "engines": { 391 | "node": ">= 0.8" 392 | } 393 | }, 394 | "node_modules/path-to-regexp": { 395 | "version": "0.1.7", 396 | "license": "MIT" 397 | }, 398 | "node_modules/proxy-addr": { 399 | "version": "2.0.7", 400 | "license": "MIT", 401 | "dependencies": { 402 | "forwarded": "0.2.0", 403 | "ipaddr.js": "1.9.1" 404 | }, 405 | "engines": { 406 | "node": ">= 0.10" 407 | } 408 | }, 409 | "node_modules/qs": { 410 | "version": "6.11.0", 411 | "license": "BSD-3-Clause", 412 | "dependencies": { 413 | "side-channel": "^1.0.4" 414 | }, 415 | "engines": { 416 | "node": ">=0.6" 417 | }, 418 | "funding": { 419 | "url": "https://github.com/sponsors/ljharb" 420 | } 421 | }, 422 | "node_modules/range-parser": { 423 | "version": "1.2.1", 424 | "license": "MIT", 425 | "engines": { 426 | "node": ">= 0.6" 427 | } 428 | }, 429 | "node_modules/raw-body": { 430 | "version": "2.5.1", 431 | "license": "MIT", 432 | "dependencies": { 433 | "bytes": "3.1.2", 434 | "http-errors": "2.0.0", 435 | "iconv-lite": "0.4.24", 436 | "unpipe": "1.0.0" 437 | }, 438 | "engines": { 439 | "node": ">= 0.8" 440 | } 441 | }, 442 | "node_modules/safe-buffer": { 443 | "version": "5.2.1", 444 | "funding": [ 445 | { 446 | "type": "github", 447 | "url": "https://github.com/sponsors/feross" 448 | }, 449 | { 450 | "type": "patreon", 451 | "url": "https://www.patreon.com/feross" 452 | }, 453 | { 454 | "type": "consulting", 455 | "url": "https://feross.org/support" 456 | } 457 | ], 458 | "license": "MIT" 459 | }, 460 | "node_modules/safer-buffer": { 461 | "version": "2.1.2", 462 | "license": "MIT" 463 | }, 464 | "node_modules/send": { 465 | "version": "0.18.0", 466 | "license": "MIT", 467 | "dependencies": { 468 | "debug": "2.6.9", 469 | "depd": "2.0.0", 470 | "destroy": "1.2.0", 471 | "encodeurl": "~1.0.2", 472 | "escape-html": "~1.0.3", 473 | "etag": "~1.8.1", 474 | "fresh": "0.5.2", 475 | "http-errors": "2.0.0", 476 | "mime": "1.6.0", 477 | "ms": "2.1.3", 478 | "on-finished": "2.4.1", 479 | "range-parser": "~1.2.1", 480 | "statuses": "2.0.1" 481 | }, 482 | "engines": { 483 | "node": ">= 0.8.0" 484 | } 485 | }, 486 | "node_modules/send/node_modules/ms": { 487 | "version": "2.1.3", 488 | "license": "MIT" 489 | }, 490 | "node_modules/serve-static": { 491 | "version": "1.15.0", 492 | "license": "MIT", 493 | "dependencies": { 494 | "encodeurl": "~1.0.2", 495 | "escape-html": "~1.0.3", 496 | "parseurl": "~1.3.3", 497 | "send": "0.18.0" 498 | }, 499 | "engines": { 500 | "node": ">= 0.8.0" 501 | } 502 | }, 503 | "node_modules/setprototypeof": { 504 | "version": "1.2.0", 505 | "license": "ISC" 506 | }, 507 | "node_modules/side-channel": { 508 | "version": "1.0.4", 509 | "license": "MIT", 510 | "dependencies": { 511 | "call-bind": "^1.0.0", 512 | "get-intrinsic": "^1.0.2", 513 | "object-inspect": "^1.9.0" 514 | }, 515 | "funding": { 516 | "url": "https://github.com/sponsors/ljharb" 517 | } 518 | }, 519 | "node_modules/statuses": { 520 | "version": "2.0.1", 521 | "license": "MIT", 522 | "engines": { 523 | "node": ">= 0.8" 524 | } 525 | }, 526 | "node_modules/toidentifier": { 527 | "version": "1.0.1", 528 | "license": "MIT", 529 | "engines": { 530 | "node": ">=0.6" 531 | } 532 | }, 533 | "node_modules/type-is": { 534 | "version": "1.6.18", 535 | "license": "MIT", 536 | "dependencies": { 537 | "media-typer": "0.3.0", 538 | "mime-types": "~2.1.24" 539 | }, 540 | "engines": { 541 | "node": ">= 0.6" 542 | } 543 | }, 544 | "node_modules/unpipe": { 545 | "version": "1.0.0", 546 | "license": "MIT", 547 | "engines": { 548 | "node": ">= 0.8" 549 | } 550 | }, 551 | "node_modules/utils-merge": { 552 | "version": "1.0.1", 553 | "license": "MIT", 554 | "engines": { 555 | "node": ">= 0.4.0" 556 | } 557 | }, 558 | "node_modules/vary": { 559 | "version": "1.1.2", 560 | "license": "MIT", 561 | "engines": { 562 | "node": ">= 0.8" 563 | } 564 | }, 565 | "node_modules/zod": { 566 | "version": "3.21.4", 567 | "license": "MIT", 568 | "funding": { 569 | "url": "https://github.com/sponsors/colinhacks" 570 | } 571 | } 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cors": "2.8.5", 15 | "express": "4.18.2", 16 | "zod": "3.21.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /schemas/movies.js: -------------------------------------------------------------------------------- 1 | const z = require('zod') 2 | 3 | const movieSchema = z.object({ 4 | title: z.string({ 5 | invalid_type_error: 'Movie title must be a string', 6 | required_error: 'Movie title is required.' 7 | }), 8 | year: z.number().int().min(1900).max(2024), 9 | director: z.string(), 10 | duration: z.number().int().positive(), 11 | rate: z.number().min(0).max(10).default(5), 12 | poster: z.string().url({ 13 | message: 'Poster must be a valid URL' 14 | }), 15 | genre: z.array( 16 | z.enum(['Action', 'Adventure', 'Crime', 'Comedy', 'Drama', 'Fantasy', 'Horror', 'Thriller', 'Sci-Fi']), 17 | { 18 | required_error: 'Movie genre is required.', 19 | invalid_type_error: 'Movie genre must be an array of enum Genre' 20 | } 21 | ) 22 | }) 23 | 24 | function validateMovie (input) { 25 | return movieSchema.safeParse(input) 26 | } 27 | 28 | function validatePartialMovie (input) { 29 | return movieSchema.partial().safeParse(input) 30 | } 31 | 32 | module.exports = { 33 | validateMovie, 34 | validatePartialMovie 35 | } 36 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |